1use rhdl::prelude::*;
15use rhdl_fpga::core::ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite};
16
17use crate::field_parallel::FieldElement51;
18
19#[derive(Copy, Clone, Debug, PartialEq, Digital)]
21pub struct NielsPoint {
22 pub y_plus_x: FieldElement51,
24 pub y_minus_x: FieldElement51,
26 pub xy2d: FieldElement51,
28}
29
30impl NielsPoint {
31 pub fn zero() -> Self {
33 Self {
34 y_plus_x: FieldElement51::zero(),
35 y_minus_x: FieldElement51::zero(),
36 xy2d: FieldElement51::zero(),
37 }
38 }
39}
40
41include!(concat!(env!("OUT_DIR"), "/multicomb_table_data.rs"));
42
43#[derive(Copy, Clone, Debug, PartialEq, Digital)]
44#[doc(hidden)]
45pub struct ScanBank8In {
46 addresses: [b7; 8],
47}
48
49#[derive(Copy, Clone, Debug, PartialEq, Digital)]
50#[doc(hidden)]
51pub struct ScanBank8Out {
52 words: [NielsPoint; 8],
53}
54
55#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
57#[doc(hidden)]
58pub struct ScanBank8 {
59 port0: SyncBRAM<NielsPoint, 7>,
60 port1: SyncBRAM<NielsPoint, 7>,
61 port2: SyncBRAM<NielsPoint, 7>,
62 port3: SyncBRAM<NielsPoint, 7>,
63 port4: SyncBRAM<NielsPoint, 7>,
64 port5: SyncBRAM<NielsPoint, 7>,
65 port6: SyncBRAM<NielsPoint, 7>,
66 port7: SyncBRAM<NielsPoint, 7>,
67}
68
69impl ScanBank8 {
70 fn new(initial: Vec<(b7, NielsPoint)>) -> Self {
71 Self {
72 port0: SyncBRAM::new(initial.clone()),
73 port1: SyncBRAM::new(initial.clone()),
74 port2: SyncBRAM::new(initial.clone()),
75 port3: SyncBRAM::new(initial.clone()),
76 port4: SyncBRAM::new(initial.clone()),
77 port5: SyncBRAM::new(initial.clone()),
78 port6: SyncBRAM::new(initial.clone()),
79 port7: SyncBRAM::new(initial),
80 }
81 }
82}
83
84impl SynchronousIO for ScanBank8 {
85 type I = ScanBank8In;
86 type O = ScanBank8Out;
87 type Kernel = scan_bank8_kernel;
88}
89
90#[kernel]
91#[doc(hidden)]
92pub fn scan_bank8_kernel(
93 _cr: ClockReset,
94 i: ScanBank8In,
95 q: ScanBank8Q,
96) -> (ScanBank8Out, ScanBank8D) {
97 let output = ScanBank8Out {
98 words: [
99 q.port0, q.port1, q.port2, q.port3, q.port4, q.port5, q.port6, q.port7,
100 ],
101 };
102 let disabled = RamWrite::<NielsPoint, 7> {
103 addr: b7(0),
104 value: NielsPoint {
105 y_plus_x: FieldElement51 { limbs: [b51(0); 5] },
106 y_minus_x: FieldElement51 { limbs: [b51(0); 5] },
107 xy2d: FieldElement51 { limbs: [b51(0); 5] },
108 },
109 enable: false,
110 };
111 let mut d = ScanBank8D::dont_care();
112 d.port0 = RamIn::<NielsPoint, 7> {
113 read_addr: i.addresses[0],
114 write: disabled,
115 };
116 d.port1 = RamIn::<NielsPoint, 7> {
117 read_addr: i.addresses[1],
118 write: disabled,
119 };
120 d.port2 = RamIn::<NielsPoint, 7> {
121 read_addr: i.addresses[2],
122 write: disabled,
123 };
124 d.port3 = RamIn::<NielsPoint, 7> {
125 read_addr: i.addresses[3],
126 write: disabled,
127 };
128 d.port4 = RamIn::<NielsPoint, 7> {
129 read_addr: i.addresses[4],
130 write: disabled,
131 };
132 d.port5 = RamIn::<NielsPoint, 7> {
133 read_addr: i.addresses[5],
134 write: disabled,
135 };
136 d.port6 = RamIn::<NielsPoint, 7> {
137 read_addr: i.addresses[6],
138 write: disabled,
139 };
140 d.port7 = RamIn::<NielsPoint, 7> {
141 read_addr: i.addresses[7],
142 write: disabled,
143 };
144 (output, d)
145}
146
147#[derive(Copy, Clone, Debug, PartialEq, Digital)]
149pub struct MultiCombLookupIn {
150 pub valid: bool,
152 pub block: b2,
154 pub candidate: b7,
156 pub sign: bool,
158 pub tag: b16,
160}
161
162#[derive(Copy, Clone, Debug, PartialEq, Digital)]
164pub struct MultiCombLookupOut {
165 pub ready: bool,
167 pub valid: bool,
169 pub point: NielsPoint,
171 pub tag: b16,
173}
174
175#[derive(Copy, Clone, Debug, PartialEq, Digital)]
176#[doc(hidden)]
177pub struct MultiCombLookupRegs {
178 active: bool,
179 phase: b2,
180 block: b2,
181 candidate: b7,
182 sign: bool,
183 tag: b16,
184 groups: [b5; 8],
185 selected: NielsPoint,
186}
187
188#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
190#[rhdl(dq_no_prefix)]
191pub struct MultiCombLookup {
192 regs: rhdl_fpga::core::dff::DFF<MultiCombLookupRegs>,
193 bank0: ScanBank8,
194 bank1: ScanBank8,
195 bank2: ScanBank8,
196 bank3: ScanBank8,
197}
198
199impl Default for MultiCombLookup {
200 fn default() -> Self {
201 Self {
202 regs: rhdl_fpga::core::dff::DFF::new(MultiCombLookupRegs {
203 active: false,
204 phase: b2(0),
205 block: b2(0),
206 candidate: b7(0),
207 sign: false,
208 tag: b16(0),
209 groups: [b5(0); 8],
210 selected: NielsPoint::zero(),
211 }),
212 bank0: ScanBank8::new(table_bank_0()),
213 bank1: ScanBank8::new(table_bank_1()),
214 bank2: ScanBank8::new(table_bank_2()),
215 bank3: ScanBank8::new(table_bank_3()),
216 }
217 }
218}
219
220impl SynchronousIO for MultiCombLookup {
221 type I = MultiCombLookupIn;
222 type O = MultiCombLookupOut;
223 type Kernel = multicomb_lookup_kernel;
224}
225
226#[kernel]
227fn negate_field(value: FieldElement51) -> FieldElement51 {
228 let mask = b52(0x7ffffffffffff);
229 let p0 = b52(0x7ffffffffffed);
230 let d0 = p0 + b52(0x8000000000000) - value.limbs[0].resize::<52>();
231 let borrow0 = (d0 >> 51) == b52(0);
232 let d1 = mask + b52(0x8000000000000)
233 - value.limbs[1].resize::<52>()
234 - if borrow0 { b52(1) } else { b52(0) };
235 let borrow1 = (d1 >> 51) == b52(0);
236 let d2 = mask + b52(0x8000000000000)
237 - value.limbs[2].resize::<52>()
238 - if borrow1 { b52(1) } else { b52(0) };
239 let borrow2 = (d2 >> 51) == b52(0);
240 let d3 = mask + b52(0x8000000000000)
241 - value.limbs[3].resize::<52>()
242 - if borrow2 { b52(1) } else { b52(0) };
243 let borrow3 = (d3 >> 51) == b52(0);
244 let d4 = mask + b52(0x8000000000000)
245 - value.limbs[4].resize::<52>()
246 - if borrow3 { b52(1) } else { b52(0) };
247 let is_zero = value.limbs[0] == b51(0)
248 && value.limbs[1] == b51(0)
249 && value.limbs[2] == b51(0)
250 && value.limbs[3] == b51(0)
251 && value.limbs[4] == b51(0);
252 if is_zero {
253 FieldElement51 { limbs: [b51(0); 5] }
254 } else {
255 FieldElement51 {
256 limbs: [
257 d0.resize::<51>(),
258 d1.resize::<51>(),
259 d2.resize::<51>(),
260 d3.resize::<51>(),
261 d4.resize::<51>(),
262 ],
263 }
264 }
265}
266
267#[kernel]
269pub fn multicomb_lookup_kernel(
270 _cr: ClockReset,
271 i: MultiCombLookupIn,
272 q: Q,
273) -> (MultiCombLookupOut, D) {
274 let r = q.regs;
275 let mut n = r;
276 let mut selected = r.selected;
277 let mut output = MultiCombLookupOut {
278 ready: !r.active,
279 valid: false,
280 point: NielsPoint {
281 y_plus_x: FieldElement51 { limbs: [b51(0); 5] },
282 y_minus_x: FieldElement51 { limbs: [b51(0); 5] },
283 xy2d: FieldElement51 { limbs: [b51(0); 5] },
284 },
285 tag: r.tag,
286 };
287
288 let mut addresses = [b7(0); 8];
289 let address_block = if !r.active { i.block } else { r.block };
290 let address_phase = if !r.active { b2(0) } else { r.phase + b2(1) };
291 for port in 0..8 {
292 let group = address_phase.resize::<7>() * b7(8) + b7(port as u128);
293 addresses[port] = (address_block.resize::<7>() << 5) | group;
294 }
295
296 if i.valid && !r.active {
297 n.active = true;
298 n.phase = b2(0);
299 n.block = i.block;
300 n.candidate = i.candidate;
301 n.sign = i.sign;
302 n.tag = i.tag;
303 n.selected = NielsPoint {
304 y_plus_x: FieldElement51 { limbs: [b51(0); 5] },
305 y_minus_x: FieldElement51 { limbs: [b51(0); 5] },
306 xy2d: FieldElement51 { limbs: [b51(0); 5] },
307 };
308 for port in 0..8 {
309 n.groups[port] = b5(port as u128);
310 }
311 } else if r.active {
312 for port in 0..8 {
313 let bank_word = if r.candidate.resize::<2>() == b2(0) {
314 q.bank0.words[port]
315 } else if r.candidate.resize::<2>() == b2(1) {
316 q.bank1.words[port]
317 } else if r.candidate.resize::<2>() == b2(2) {
318 q.bank2.words[port]
319 } else {
320 q.bank3.words[port]
321 };
322 if (r.candidate >> 2).resize::<5>() == r.groups[port] {
323 selected = bank_word;
324 }
325 }
326 n.selected = selected;
327 if r.phase == b2(3) {
328 n.active = false;
329 output.valid = true;
330 output.tag = r.tag;
331 output.point = if r.sign {
332 NielsPoint {
333 y_plus_x: selected.y_minus_x,
334 y_minus_x: selected.y_plus_x,
335 xy2d: negate_field(selected.xy2d),
336 }
337 } else {
338 selected
339 };
340 } else {
341 n.phase = r.phase + b2(1);
342 for port in 0..8 {
343 n.groups[port] = r.groups[port] + b5(8);
344 }
345 }
346 }
347
348 let ram_input = ScanBank8In { addresses };
349 let mut d = D::dont_care();
350 d.regs = n;
351 d.bank0 = ram_input;
352 d.bank1 = ram_input;
353 d.bank2 = ram_input;
354 d.bank3 = ram_input;
355 (output, d)
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use std::iter;
362
363 fn idle() -> MultiCombLookupIn {
364 MultiCombLookupIn {
365 valid: false,
366 block: b2(0),
367 candidate: b7(0),
368 sign: false,
369 tag: b16(0),
370 }
371 }
372
373 fn neg(value: FieldElement51) -> FieldElement51 {
374 negate_field(value)
375 }
376
377 #[test]
378 fn scans_every_table_entry_and_applies_sign() {
379 let banks = [
380 table_bank_0(),
381 table_bank_1(),
382 table_bank_2(),
383 table_bank_3(),
384 ];
385 let mut inputs = vec![idle()];
386 let mut expected = Vec::new();
387 for block in 0usize..4 {
388 for candidate in 0usize..128 {
389 for sign in [false, true] {
390 let request = MultiCombLookupIn {
391 valid: true,
392 block: b2(block as u128),
393 candidate: b7(candidate as u128),
394 sign,
395 tag: b16((block * 256 + candidate * 2 + usize::from(sign)) as u128),
396 };
397 inputs.push(request);
398 inputs.extend(iter::repeat_n(idle(), 5));
399 let table_point = banks[candidate & 3][block * 32 + (candidate >> 2)].1;
400 let point = if sign {
401 NielsPoint {
402 y_plus_x: table_point.y_minus_x,
403 y_minus_x: table_point.y_plus_x,
404 xy2d: neg(table_point.xy2d),
405 }
406 } else {
407 table_point
408 };
409 expected.push((request.tag, point));
410 }
411 }
412 }
413 inputs.extend(iter::repeat_n(idle(), 8));
414 let outputs: Vec<_> = MultiCombLookup::default()
415 .run(inputs.into_iter().with_reset(1).clock_pos_edge(100))
416 .synchronous_sample()
417 .map(|sample| sample.output)
418 .filter(|output| output.valid)
419 .collect();
420 assert_eq!(outputs.len(), expected.len());
421 for (output, (tag, point)) in outputs.into_iter().zip(expected) {
422 assert_eq!(output.tag, tag);
423 assert_eq!(output.point, point);
424 }
425 }
426
427 #[test]
428 fn compiles_to_native_rhdl_netlist() -> miette::Result<()> {
429 MultiCombLookup::default()
430 .descriptor(ScopedName::top())?
431 .hdl()?;
432 Ok(())
433 }
434}