1use rhdl::prelude::*;
9use rhdl_fpga::core::dff::DFF;
10
11use crate::{
12 field_parallel::{
13 FieldElement51, FieldMulIn, FieldMulPipe, field_add_kernel, field_eq_kernel,
14 field_sub_kernel,
15 },
16 multicomb::ExtendedPoint,
17};
18
19const CONTEXTS: usize = 20;
20const FREE: u128 = 0;
21const READY: u128 = 1;
22const WAIT: u128 = 2;
23
24#[derive(Copy, Clone, Debug, Default, PartialEq, Digital)]
25#[doc(hidden)]
26pub enum EquationPhase {
27 #[default]
28 AddFirst,
29 AddSecond,
30 Compare,
31}
32
33#[derive(Copy, Clone, Debug, PartialEq, Digital)]
35pub struct EquationIn {
36 pub valid: bool,
38 pub variable: ExtendedPoint,
40 pub r: ExtendedPoint,
42 pub r_t2d: FieldElement51,
44 pub fixed: ExtendedPoint,
46 pub tag: b16,
48}
49
50#[derive(Copy, Clone, Debug, PartialEq, Digital)]
52pub struct EquationOut {
53 pub ready: bool,
55 pub valid: bool,
57 pub equal: bool,
59 pub tag: b16,
61 pub occupancy: b5,
63}
64
65#[derive(Copy, Clone, Debug, PartialEq, Digital)]
66#[doc(hidden)]
67pub struct EquationContext {
68 state: b2,
69 phase: EquationPhase,
70 tag: b16,
71 variable: ExtendedPoint,
72 r: ExtendedPoint,
73 r_t2d: FieldElement51,
74 fixed: ExtendedPoint,
75 e: FieldElement51,
76 f: FieldElement51,
77 g: FieldElement51,
78 h: FieldElement51,
79 sum: ExtendedPoint,
80}
81
82fn empty_context() -> EquationContext {
83 EquationContext {
84 state: b2(FREE),
85 phase: EquationPhase::AddFirst,
86 tag: b16(0),
87 variable: ExtendedPoint::identity(),
88 r: ExtendedPoint::identity(),
89 r_t2d: FieldElement51::zero(),
90 fixed: ExtendedPoint::identity(),
91 e: FieldElement51::zero(),
92 f: FieldElement51::zero(),
93 g: FieldElement51::zero(),
94 h: FieldElement51::zero(),
95 sum: ExtendedPoint::identity(),
96 }
97}
98
99#[derive(Copy, Clone, Debug, PartialEq, Digital)]
100#[doc(hidden)]
101pub struct EquationControl {
102 contexts: [EquationContext; CONTEXTS],
103 issue_valid: [bool; 17],
104 issue_context: [b5; 17],
105 round_robin: b5,
106}
107
108fn reset_control() -> EquationControl {
109 EquationControl {
110 contexts: [empty_context(); CONTEXTS],
111 issue_valid: [false; 17],
112 issue_context: [b5(0); 17],
113 round_robin: b5(0),
114 }
115}
116
117#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
119pub struct EquationPipe {
120 control: DFF<EquationControl>,
121 mul0: FieldMulPipe,
122 mul1: FieldMulPipe,
123 mul2: FieldMulPipe,
124 mul3: FieldMulPipe,
125}
126
127impl Default for EquationPipe {
128 fn default() -> Self {
129 Self {
130 control: DFF::new(reset_control()),
131 mul0: FieldMulPipe::default(),
132 mul1: FieldMulPipe::default(),
133 mul2: FieldMulPipe::default(),
134 mul3: FieldMulPipe::default(),
135 }
136 }
137}
138
139impl SynchronousIO for EquationPipe {
140 type I = EquationIn;
141 type O = EquationOut;
142 type Kernel = equation_kernel;
143}
144
145#[kernel]
146fn zero() -> FieldElement51 {
147 FieldElement51 { limbs: [b51(0); 5] }
148}
149
150#[kernel]
151fn one() -> FieldElement51 {
152 FieldElement51 {
153 limbs: [b51(1), b51(0), b51(0), b51(0), b51(0)],
154 }
155}
156
157#[kernel]
158fn identity() -> ExtendedPoint {
159 ExtendedPoint {
160 x: zero(),
161 y: one(),
162 z: one(),
163 t: zero(),
164 }
165}
166
167#[kernel]
168fn empty_context_kernel(_unused: bool) -> EquationContext {
169 EquationContext {
170 state: b2(FREE),
171 phase: EquationPhase::AddFirst,
172 tag: b16(0),
173 variable: identity(),
174 r: identity(),
175 r_t2d: zero(),
176 fixed: identity(),
177 e: zero(),
178 f: zero(),
179 g: zero(),
180 h: zero(),
181 sum: identity(),
182 }
183}
184
185#[kernel]
186fn wrap20(value: b5) -> b5 {
187 if value >= b5(20) {
188 value - b5(20)
189 } else {
190 value
191 }
192}
193
194#[kernel]
196pub fn equation_kernel(
197 _cr: ClockReset,
198 i: EquationIn,
199 q: EquationPipeQ,
200) -> (EquationOut, EquationPipeD) {
201 let r = q.control;
202 let mut n = r;
203 let zero = zero();
204 let mut output = EquationOut {
205 ready: false,
206 valid: false,
207 equal: false,
208 tag: b16(0),
209 occupancy: b5(0),
210 };
211
212 let mut free_found = false;
213 let mut free_context = b5(0);
214 for index in 0..CONTEXTS {
215 if r.contexts[index].state != b2(FREE) {
216 output.occupancy += b5(1);
217 } else if !free_found {
218 free_found = true;
219 free_context = b5(index as u128);
220 }
221 }
222 output.ready = free_found;
223
224 let mut issue_found = false;
225 let mut issue_context = b5(0);
226 for offset in 0..CONTEXTS {
227 let candidate = wrap20(r.round_robin + b5(offset as u128));
228 if !issue_found && r.contexts[candidate].state == b2(READY) {
229 issue_found = true;
230 issue_context = candidate;
231 }
232 }
233 let context = r.contexts[issue_context];
234 let mut a = [zero; 4];
235 let mut b = [zero; 4];
236 if issue_found {
237 match context.phase {
238 EquationPhase::AddFirst => {
239 a[0] = field_sub_kernel(context.variable.y, context.variable.x);
240 b[0] = field_sub_kernel(context.r.y, context.r.x);
241 a[1] = field_add_kernel(context.variable.y, context.variable.x);
242 b[1] = field_add_kernel(context.r.y, context.r.x);
243 a[2] = context.variable.t;
244 b[2] = context.r_t2d;
245 }
246 EquationPhase::AddSecond => {
247 a = [context.e, context.g, context.e, context.f];
248 b = [context.f, context.h, context.h, context.g];
249 }
250 EquationPhase::Compare => {
251 a = [
252 context.sum.x,
253 context.fixed.x,
254 context.sum.y,
255 context.fixed.y,
256 ];
257 b = [
258 context.fixed.z,
259 context.sum.z,
260 context.fixed.z,
261 context.sum.z,
262 ];
263 }
264 }
265 n.contexts[issue_context].state = b2(WAIT);
266 n.round_robin = wrap20(issue_context + b5(1));
267 }
268
269 n.issue_valid[0] = issue_found;
270 n.issue_context[0] = issue_context;
271 for stage in 1..17 {
272 n.issue_valid[stage] = r.issue_valid[stage - 1];
273 n.issue_context[stage] = r.issue_context[stage - 1];
274 }
275
276 if r.issue_valid[16] && q.mul0.valid && q.mul1.valid && q.mul2.valid && q.mul3.valid {
277 let result_context = r.issue_context[16];
278 let current = r.contexts[result_context];
279 let mut updated = current;
280 updated.state = b2(READY);
281 match current.phase {
282 EquationPhase::AddFirst => {
283 let d = field_add_kernel(current.variable.z, current.variable.z);
284 updated.e = field_sub_kernel(q.mul1.result, q.mul0.result);
285 updated.f = field_sub_kernel(d, q.mul2.result);
286 updated.g = field_add_kernel(d, q.mul2.result);
287 updated.h = field_add_kernel(q.mul1.result, q.mul0.result);
288 updated.phase = EquationPhase::AddSecond;
289 }
290 EquationPhase::AddSecond => {
291 updated.sum = ExtendedPoint {
292 x: q.mul0.result,
293 y: q.mul1.result,
294 z: q.mul3.result,
295 t: q.mul2.result,
296 };
297 updated.phase = EquationPhase::Compare;
298 }
299 EquationPhase::Compare => {
300 output.valid = true;
301 output.equal = field_eq_kernel(q.mul0.result, q.mul1.result)
302 && field_eq_kernel(q.mul2.result, q.mul3.result);
303 output.tag = current.tag;
304 updated = empty_context_kernel(false);
305 }
306 }
307 n.contexts[result_context] = updated;
308 }
309
310 if i.valid && free_found {
311 n.contexts[free_context] = EquationContext {
312 state: b2(READY),
313 phase: EquationPhase::AddFirst,
314 tag: i.tag,
315 variable: i.variable,
316 r: i.r,
317 r_t2d: i.r_t2d,
318 fixed: i.fixed,
319 e: zero,
320 f: zero,
321 g: zero,
322 h: zero,
323 sum: identity(),
324 };
325 }
326
327 let mut d = EquationPipeD::dont_care();
328 d.control = n;
329 d.mul0 = FieldMulIn {
330 valid: issue_found,
331 a: a[0],
332 b: b[0],
333 };
334 d.mul1 = FieldMulIn {
335 valid: issue_found,
336 a: a[1],
337 b: b[1],
338 };
339 d.mul2 = FieldMulIn {
340 valid: issue_found,
341 a: a[2],
342 b: b[2],
343 };
344 d.mul3 = FieldMulIn {
345 valid: issue_found,
346 a: a[3],
347 b: b[3],
348 };
349 (output, d)
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::field_parallel::field_from_words_kernel;
356 use std::iter;
357
358 fn field(words: [u64; 4]) -> FieldElement51 {
359 field_from_words_kernel(words.map(|word| b64(word as u128)))
360 }
361
362 fn basepoint() -> (ExtendedPoint, FieldElement51) {
363 (
364 ExtendedPoint {
365 x: field([
366 0xc9562d608f25d51a,
367 0x692cc7609525a7b2,
368 0xc0a4e231fdd6dc5c,
369 0x216936d3cd6e53fe,
370 ]),
371 y: field([
372 0x6666666666666658,
373 0x6666666666666666,
374 0x6666666666666666,
375 0x6666666666666666,
376 ]),
377 z: FieldElement51::one(),
378 t: field([
379 0x6dde8ab3a5b7dda3,
380 0x20f09f80775152f5,
381 0x66ea4e8e64abe37d,
382 0x67875f0fd78b7665,
383 ]),
384 },
385 field([
386 0xabc91205877aaa68,
387 0x26d9e823ccaac49e,
388 0x5a1b7dcbdd43598c,
389 0x6f117b689f0c65a8,
390 ]),
391 )
392 }
393
394 #[test]
395 fn identity_plus_basepoint_compares_equal_to_basepoint() {
396 let (base, t2d) = basepoint();
397 let request = EquationIn {
398 valid: true,
399 variable: ExtendedPoint::identity(),
400 r: base,
401 r_t2d: t2d,
402 fixed: base,
403 tag: b16(7),
404 };
405 let idle = EquationIn {
406 valid: false,
407 ..request
408 };
409 let outputs: Vec<_> = EquationPipe::default()
410 .run(
411 iter::once(idle)
412 .chain(iter::once(request))
413 .chain(iter::repeat_n(idle, 100))
414 .with_reset(1)
415 .clock_pos_edge(100),
416 )
417 .synchronous_sample()
418 .map(|sample| sample.output)
419 .filter(|output| output.valid)
420 .collect();
421 assert_eq!(outputs.len(), 1);
422 assert!(outputs[0].equal);
423 assert_eq!(outputs[0].tag, b16(7));
424 }
425
426 #[test]
427 fn basepoint_plus_basepoint_compares_equal_to_double() {
428 let (base, t2d) = basepoint();
429 let double = ExtendedPoint {
430 x: field([
431 0x83c5a14e2843ce0e,
432 0x080d8e4515d7a45f,
433 0x3d043b7d1833e7ac,
434 0x36ab384c9f5a046c,
435 ]),
436 y: field([
437 0x0e5f46ae6af8a3c9,
438 0x97390f5164385156,
439 0x1da25ee8c9a21f56,
440 0x2260cdf3092329c2,
441 ]),
442 z: FieldElement51::one(),
443 t: field([
444 0xb71a3f556d69b401,
445 0x5d79acc9c1f72402,
446 0x1de55f087303b413,
447 0x2498a7850b2f68dc,
448 ]),
449 };
450 let request = EquationIn {
451 valid: true,
452 variable: base,
453 r: base,
454 r_t2d: t2d,
455 fixed: double,
456 tag: b16(8),
457 };
458 let idle = EquationIn {
459 valid: false,
460 ..request
461 };
462 let output = EquationPipe::default()
463 .run(
464 iter::once(idle)
465 .chain(iter::once(request))
466 .chain(iter::repeat_n(idle, 100))
467 .with_reset(1)
468 .clock_pos_edge(100),
469 )
470 .synchronous_sample()
471 .map(|sample| sample.output)
472 .find(|output| output.valid)
473 .unwrap();
474 assert!(output.equal);
475 assert_eq!(output.tag, b16(8));
476 }
477}