Skip to main content

rhdl_ed25519_fast_core/
scalar_parallel.rs

1//! Initiation-interval-one scalar arithmetic for Ed25519.
2//!
3//! The group order is `l = 2^252 + q`, where `q` is only 125 bits. Reduction
4//! therefore folds high bits through multiplication by the fixed eight-limb
5//! radix-`2^16` constant `q`. The multiplier below is fully spatial: each of 24
6//! coefficients owns eight registered `16 x 16` products, and normalization
7//! advances two limbs per stage.
8
9use 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/// Seventeen radix-`2^16` input limbs, least significant first.
18#[derive(Copy, Clone, Debug, PartialEq, Digital)]
19pub struct Radix16X17 {
20    /// Input limbs.
21    pub limbs: [b16; 17],
22}
23
24/// Twenty-five radix-`2^16` product limbs, least significant first.
25#[derive(Copy, Clone, Debug, PartialEq, Digital)]
26pub struct Radix16X25 {
27    /// Product limbs.
28    pub limbs: [b16; 25],
29}
30
31/// Tagged input to [`ConstQMulPipe`].
32#[derive(Copy, Clone, Debug, PartialEq, Digital)]
33pub struct ConstQMulIn {
34    /// Marks this transaction valid.
35    pub valid: bool,
36    /// Multiplicand.
37    pub x: Radix16X17,
38    /// Caller-defined transaction tag.
39    pub tag: b16,
40}
41
42/// Tagged output from [`ConstQMulPipe`].
43#[derive(Copy, Clone, Debug, PartialEq, Digital)]
44pub struct ConstQMulOut {
45    /// Marks this transaction valid.
46    pub valid: bool,
47    /// Exact product `x * q`.
48    pub product: Radix16X25,
49    /// Delayed caller tag.
50    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/// Pipeline transition for one constant-product coefficient.
102#[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/// Wiring transition for four coefficient cells.
168#[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/// Two-limb carry-normalization transition.
237#[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/// Wiring transition for four registered normalization steps.
286#[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/// Seventeen-stage multiplication by the Ed25519 reduction constant `q`.
319///
320/// The pipeline accepts one multiplication every cycle and contains 192
321/// independent `16 x 16` multiply sites.
322#[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/// State transition and hierarchy wiring for [`ConstQMulPipe`].
363#[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/// Seventeen-cycle sideband delay transition.
527#[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/// Tagged 512-bit reduction request for [`ScalarReducePipe`].
573#[derive(Copy, Clone, Debug, PartialEq, Digital)]
574pub struct ScalarReducePipeIn {
575    /// Marks this digest valid.
576    pub valid: bool,
577    /// Wide little-endian input.
578    pub wide: Words512,
579    /// Caller-defined transaction tag.
580    pub tag: b16,
581}
582
583/// Tagged canonical scalar from [`ScalarReducePipe`].
584#[derive(Copy, Clone, Debug, PartialEq, Digital)]
585pub struct ScalarReducePipeOut {
586    /// Marks this result valid.
587    pub valid: bool,
588    /// Canonical scalar strictly below the Ed25519 group order.
589    pub scalar: ScalarWords,
590    /// Delayed transaction tag.
591    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/// Fifty-seven-stage, initiation-interval-one reduction modulo `l`.
619///
620/// Three fixed-constant folds remove bits above bit 251. Six final registered
621/// add/subtract stages canonicalize the result. Every loop count and data path
622/// is independent of the input value.
623#[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/// State transition for [`ScalarReducePipe`].
743#[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/// Pipeline transition for one coefficient of a full scalar product.
929#[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/// Wiring transition for four full-product coefficients.
990#[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/// Wiring transition for eight full-product coefficients.
1052#[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/// Two-limb full-product normalization transition.
1129#[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/// Wiring transition for four full-product normalization steps.
1178#[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/// Exact 528-bit output of [`ScalarWideMulPipe`].
1211#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1212pub struct Radix16X33 {
1213    /// Product limbs, least significant first.
1214    pub limbs: [b16; 33],
1215}
1216
1217/// Tagged request for [`ScalarWideMulPipe`].
1218#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1219pub struct ScalarWideMulIn {
1220    /// Marks this request valid.
1221    pub valid: bool,
1222    /// First factor.
1223    pub x: ScalarWords,
1224    /// Second factor.
1225    pub y: ScalarWords,
1226    /// Sideband addend delayed with the product.
1227    pub addend: ScalarWords,
1228    /// Caller tag.
1229    pub tag: b16,
1230}
1231
1232/// Tagged response from [`ScalarWideMulPipe`].
1233#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1234pub struct ScalarWideMulOut {
1235    /// Marks this response valid.
1236    pub valid: bool,
1237    /// Exact product.
1238    pub product: Radix16X33,
1239    /// Delayed sideband addend.
1240    pub addend: ScalarWords,
1241    /// Delayed caller tag.
1242    pub tag: b16,
1243}
1244
1245/// Twenty-two-stage, 512-DSP full scalar multiplier.
1246#[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/// State transition for [`ScalarWideMulPipe`].
1298#[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/// Tagged scalar multiply-add request.
1429#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1430pub struct ScalarMulAddIn {
1431    /// Marks this operation valid.
1432    pub valid: bool,
1433    /// First multiplicand.
1434    pub a: ScalarWords,
1435    /// Second multiplicand.
1436    pub b: ScalarWords,
1437    /// Addend.
1438    pub c: ScalarWords,
1439    /// Caller tag.
1440    pub tag: b16,
1441}
1442
1443/// Tagged canonical scalar multiply-add response.
1444#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1445pub struct ScalarMulAddOut {
1446    /// Marks the reduced scalar valid.
1447    pub valid: bool,
1448    /// Canonical scalar result.
1449    pub scalar: ScalarWords,
1450    /// Delayed caller tag.
1451    pub tag: b16,
1452}
1453
1454/// Fully pipelined `a * b + c mod l` engine.
1455#[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/// Add a canonical scalar addend to a wide radix-`2^16` product.
1514#[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/// State transition for [`ScalarMulAddPipe`].
1538#[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}