1use crate::verify::certs::{CertRequest, CertResult, CertsInput, ENTRIES, PENDING, VERIFIED, VerifyCerts};
42use crate::verify::chains::{
43 CONTEXTS, ChainsInput, LANES, NodeWrite, NonceWrite, Retire, VerifyChains,
44};
45use crate::verify::hasher::{CheckWrite, HeaderWrite, HasherInput, Loaded, VerifyHasher};
46use crate::verify::{RECORD_BEATS, VERDICT_CERT, VERDICT_CHAINS, VERDICT_FORMAT, VERDICT_MERKLE, VERDICT_WRITTEN};
47use crate::{CHAINS, ELEMENT_BYTES};
48use rhdl::prelude::*;
49use rhdl_fpga::core::{
50 dff::DFF,
51 ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
52};
53
54pub const PENDING_VERDICTS: usize = 4096;
58const QUEUE_LIMIT: b13 = b13((PENDING_VERDICTS - 2) as u128);
60const Q_PENDING: b16 = b16(1 << 4);
62const Q_CERT_BAD: b16 = b16(1 << 3);
64const NODE_BANKS: usize = 2 * LANES;
66const NODES_AT: usize = 418;
68const LAST_BEAT: usize = RECORD_BEATS - 1;
70const MESSAGE_BEAT: usize = 25;
72const TWO_BLOCK: bool = crate::MESSAGE_BYTES == 64;
75
76#[derive(Clone, Copy, Debug, PartialEq, Digital)]
78pub struct Beat {
79 pub valid: bool,
81 pub data: [b8; 64],
83}
84
85impl Default for Beat {
86 fn default() -> Self {
88 Self {
89 valid: false,
90 data: [b8(0); 64],
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
97pub struct Verdict {
98 pub valid: bool,
100 pub byte: b8,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Digital)]
106pub struct CoreInput {
107 pub beat: Beat,
109 pub slot: b4,
112 pub check_epoch: bool,
116 pub expected_epoch: b64,
118 pub warm: CertWarm,
121 pub verdict_ready: bool,
123 pub cert_taken: bool,
125 pub cert_result: CertResult,
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Digital)]
131pub struct CertWarm {
132 pub valid: bool,
134 pub slot: b4,
136 pub commitment: [b8; 64],
138 pub signature: [b8; 64],
140}
141
142impl CertWarm {
143 #[must_use]
145 pub fn none() -> Self {
146 Self {
147 valid: false,
148 slot: b4(0),
149 commitment: [b8(0); 64],
150 signature: [b8(0); 64],
151 }
152 }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Digital)]
157pub struct CoreOutput {
158 pub beat_ready: bool,
160 pub warm_taken: bool,
162 pub verdict: Verdict,
164 pub cert_request: CertRequest,
166 pub retired: b32,
168 pub valid: b32,
170 pub cert_checks: b32,
172 pub cert_fails: b32,
174 pub ready: bool,
176 pub activity: Activity,
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
182pub struct Activity {
183 pub blake3: bool,
185 pub lanes: [bool; LANES],
187 pub beat: bool,
189 pub beat_stall: bool,
191}
192
193pub const NODE_MAP: [[(bool, usize, usize); NODE_BANKS]; RECORD_BEATS] = node_map();
199
200const fn node_map() -> [[(bool, usize, usize); NODE_BANKS]; RECORD_BEATS] {
202 let mut map = [[(false, 0, 0); NODE_BANKS]; RECORD_BEATS];
203 let mut chain = 0;
204 while chain < CHAINS {
205 let start = NODES_AT + ELEMENT_BYTES * chain;
206 let beat = (start + ELEMENT_BYTES - 1) / 64;
207 let bank = chain % NODE_BANKS;
208 assert!(!map[beat][bank].0, "two nodes of one beat share a bank");
209 map[beat][bank] = (true, start + 64 - 64 * beat, chain / NODE_BANKS);
210 chain += 1;
211 }
212 map
213}
214
215const NODE_HAS: [[bool; NODE_BANKS]; RECORD_BEATS] = node_columns().0;
217const NODE_OFFSET: [[usize; NODE_BANKS]; RECORD_BEATS] = node_columns().1;
219const NODE_SLOT: [[b4; NODE_BANKS]; RECORD_BEATS] = node_columns().2;
221
222#[allow(clippy::type_complexity)]
224const fn node_columns() -> ([[bool; NODE_BANKS]; RECORD_BEATS], [[usize; NODE_BANKS]; RECORD_BEATS], [[b4; NODE_BANKS]; RECORD_BEATS]) {
225 let mut has = [[false; NODE_BANKS]; RECORD_BEATS];
226 let mut offset = [[0; NODE_BANKS]; RECORD_BEATS];
227 let mut slot = [[b4(0); NODE_BANKS]; RECORD_BEATS];
228 let mut b = 0;
229 while b < RECORD_BEATS {
230 let mut m = 0;
231 while m < NODE_BANKS {
232 has[b][m] = NODE_MAP[b][m].0;
233 offset[b][m] = NODE_MAP[b][m].1;
234 slot[b][m] = b4(NODE_MAP[b][m].2 as u128);
235 m += 1;
236 }
237 b += 1;
238 }
239 (has, offset, slot)
240}
241
242const BIT_WRITTEN: b8 = b8(VERDICT_WRITTEN as u128);
244const BIT_FORMAT: b8 = b8(VERDICT_FORMAT as u128);
246const BIT_MERKLE: b8 = b8(VERDICT_MERKLE as u128);
248const BIT_CHAINS: b8 = b8(VERDICT_CHAINS as u128);
250const BIT_CERT: b8 = b8(VERDICT_CERT as u128);
252
253const HEADER: [b8; 16] = header_bits();
255
256const fn header_bits() -> [b8; 16] {
258 let domain = *b"DSIGFP01";
259 let mut header = [b8(0); 16];
260 let mut i = 0;
261 while i < 8 {
262 header[i] = b8(domain[i] as u128);
263 i += 1;
264 }
265 header[8] = b8(crate::certificate::PROFILE_ID as u128);
266 header[12] = b8(crate::BATCH_KEYS as u128);
267 header
268}
269
270#[derive(Clone, Copy, Debug, PartialEq, Digital)]
272#[doc(hidden)]
273pub struct CoreControl {
274 beat: b5,
276 warm_second: bool,
279 started: b32,
281 retired: b32,
283 queued: b32,
285 fetched: b32,
287 finalized: b32,
289 valid: b32,
291 reading: bool,
293 head_valid: bool,
295 head: b16,
297 format_bad: [bool; CONTEXTS],
299 cert_known: [bool; CONTEXTS],
301 cert_pending: [bool; CONTEXTS],
303 cert_entry: [b6; CONTEXTS],
305 cert_ok: [bool; CONTEXTS],
307 header_ok: bool,
309 occupancy: b7,
313 backlog: b13,
315 freed_valid: bool,
320 freed_ctx: b6,
321 oldest: b6,
324}
325
326impl Default for CoreControl {
327 fn default() -> Self {
329 Self {
330 beat: b5(0),
331 warm_second: false,
332 started: b32(0),
333 retired: b32(0),
334 queued: b32(0),
335 fetched: b32(0),
336 finalized: b32(0),
337 valid: b32(0),
338 reading: false,
339 head_valid: false,
340 head: b16(0),
341 format_bad: [false; CONTEXTS],
342 cert_known: [false; CONTEXTS],
343 cert_pending: [false; CONTEXTS],
344 cert_entry: [b6(0); CONTEXTS],
345 cert_ok: [false; CONTEXTS],
346 header_ok: false,
347 occupancy: b7(0),
348 backlog: b13(0),
349 freed_valid: false,
350 freed_ctx: b6(0),
351 oldest: b6(0),
352 }
353 }
354}
355
356#[derive(Clone, Copy, Debug, PartialEq, Digital)]
358#[doc(hidden)]
359pub struct LoaderData {
360 prev: [b8; 64],
362 root: [b8; 32],
364 pk_hash: [b8; 32],
366 nonce: [b8; 16],
368 index: b7,
370}
371
372#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
374pub struct VerifyCore<const TABLE_MASK: usize> {
375 chains: VerifyChains<TABLE_MASK>,
377 hasher: VerifyHasher,
379 certs: VerifyCerts,
381 pending: SyncBRAM<b16, 12>,
385 control: DFF<CoreControl>,
387 data: DFF<LoaderData>,
389}
390
391impl<const TABLE_MASK: usize> Default for VerifyCore<TABLE_MASK> {
392 fn default() -> Self {
394 Self {
395 chains: VerifyChains::default(),
396 hasher: VerifyHasher::default(),
397 certs: VerifyCerts::default(),
398 pending: SyncBRAM::default(),
399 control: DFF::new(CoreControl::default()),
400 data: DFF::resetless(),
401 }
402 }
403}
404
405impl<const TABLE_MASK: usize> SynchronousIO for VerifyCore<TABLE_MASK> {
406 type I = CoreInput;
407 type O = CoreOutput;
408 type Kernel = verify_core_kernel<TABLE_MASK>;
409}
410
411#[allow(clippy::needless_range_loop, clippy::too_many_lines)]
413#[kernel]
414pub fn verify_core_kernel<const TABLE_MASK: usize>(
415 clock_reset: ClockReset,
416 input: CoreInput,
417 q: VerifyCoreQ<TABLE_MASK>,
418) -> (CoreOutput, VerifyCoreD<TABLE_MASK>) {
419 let resetting = clock_reset.reset.any();
423 let _ = resetting;
424 let mut c = q.control;
425 let mut data = q.data;
426
427 let head: b6 = q.control.retired.resize();
432 let mut head_done = false;
433 let mut head_merkle = false;
434 let mut head_chains = false;
435 let mut head_format = false;
436 let mut head_known = false;
437 let mut head_pending = false;
438 let mut head_entry = b6(0);
439 let mut head_ok = false;
440 for x in 0..CONTEXTS {
441 if head == b6(x as u128) {
442 head_done = q.hasher.done[x];
443 head_merkle = q.hasher.merkle_ok[x];
444 head_chains = q.hasher.chains_ok[x];
445 head_format = q.control.format_bad[x];
446 head_known = q.control.cert_known[x];
447 head_pending = q.control.cert_pending[x];
448 head_entry = q.control.cert_entry[x];
449 head_ok = q.control.cert_ok[x];
450 }
451 }
452 let in_flight = q.control.occupancy != b7(0);
453 let queue_room = q.control.backlog < QUEUE_LIMIT;
454 let retire = in_flight && head_done && head_known && queue_room;
455 let mut entry: b16 = head_entry.resize::<16>() << 5;
456 if head_format {
458 entry = entry | BIT_FORMAT.resize::<16>();
459 }
460 if !head_merkle {
461 entry = entry | BIT_MERKLE.resize::<16>();
462 }
463 if !head_chains {
464 entry = entry | BIT_CHAINS.resize::<16>();
465 }
466 if head_pending {
467 entry = entry | Q_PENDING;
468 } else if !head_ok {
469 entry = entry | Q_CERT_BAD;
470 }
471 if retire {
472 c.retired = q.control.retired + b32(1);
473 c.queued = q.control.queued + b32(1);
474 }
475 let queue_write = RamWrite::<b16, 12> {
476 addr: q.control.queued.resize(),
477 value: entry,
478 enable: retire,
479 };
480
481 let mut queue_read: b12 = q.control.fetched.resize();
484 if q.control.reading {
485 c.head = q.pending;
486 c.head_valid = true;
487 c.reading = false;
488 } else if !q.control.head_valid && q.control.fetched != q.control.queued {
489 queue_read = q.control.fetched.resize();
490 c.reading = true;
491 c.fetched = q.control.fetched + b32(1);
492 }
493 let waiting = (q.control.head & Q_PENDING) != b16(0);
494 let wait_entry: b6 = (q.control.head >> 5).resize();
495 let mut entry_state = b2(0);
496 for e in 0..ENTRIES {
497 if wait_entry == b6(e as u128) {
498 entry_state = q.certs.state[e];
499 }
500 }
501 let settled = !waiting || entry_state != PENDING;
502 let cert_bad = if waiting { entry_state != VERIFIED } else { (q.control.head & Q_CERT_BAD) != b16(0) };
503 let finalize = q.control.head_valid && settled && input.verdict_ready;
504 let mut byte = BIT_WRITTEN | (q.control.head & b16(7)).resize::<8>();
505 if cert_bad {
506 byte = byte | BIT_CERT;
507 }
508 if finalize {
509 c.head_valid = false;
510 c.finalized = q.control.finalized + b32(1);
511 if byte == BIT_WRITTEN {
512 c.valid = q.control.valid + b32(1);
513 }
514 }
515
516 let beat = q.control.beat;
518 let ctx: b6 = q.control.started.resize::<6>() - if beat == b5(0) { b6(0) } else { b6(1) };
519 let room = q.control.occupancy < b7(CONTEXTS as u128);
520 let can_start = room && !q.certs.busy && q.chains.ready;
521 let ready = beat != b5(0) || can_start;
522 let take = input.beat.valid && ready;
523 let cur = input.beat.data;
524 let warm_first = input.warm.valid && !q.control.warm_second && beat == b5(0) && !input.beat.valid && !q.certs.busy;
527 let warm_second = q.control.warm_second;
528 c.warm_second = warm_first;
529 let mut window = [b8(0); 128];
530 for i in 0..64 {
531 window[i] = q.data.prev[i];
532 window[64 + i] = cur[i];
533 }
534 let mut header = HeaderWrite::default();
535 let mut check = CheckWrite::default();
536 let mut nonce_write = NonceWrite::default();
537 let mut loaded = Loaded::default();
538 let mut nodes = [NodeWrite::default(); NODE_BANKS];
539 if take {
540 data.prev = cur;
541 c.beat = if beat == b5(LAST_BEAT as u128) { b5(0) } else { beat + b5(1) };
542 let this: b6 = if beat == b5(0) { q.control.started.resize() } else { ctx };
544 if beat == b5(0) {
545 c.started = q.control.started + b32(1);
546 let mut same = true;
547 for i in 0..16 {
548 same = same && cur[i] == HEADER[i];
549 }
550 let mut epoch_ok = true;
552 for k in 0..8 {
553 let want: b8 = (input.expected_epoch >> ((8 * k) as u128)).resize();
554 epoch_ok = epoch_ok && cur[16 + k] == want;
555 }
556 c.header_ok = same && (epoch_ok || !input.check_epoch);
557 for i in 0..32 {
558 data.root[i] = cur[32 + i];
559 }
560 }
561 if beat == b5(2) {
562 let index_ok = cur[1] == b8(0) && cur[0] < b8(128);
563 for x in 0..CONTEXTS {
564 if this == b6(x as u128) {
565 c.format_bad[x] = !(q.control.header_ok && index_ok);
566 }
567 }
568 data.index = cur[0].resize();
569 let mut pk_hash = [b8(0); 32];
570 for i in 0..32 {
571 pk_hash[i] = cur[2 + i];
572 }
573 data.pk_hash = pk_hash;
574 let mut row = [b8(0); 64];
575 for i in 0..32 {
576 row[i] = q.data.root[i];
577 row[32 + i] = cur[2 + i];
578 }
579 check = CheckWrite {
580 enable: true,
581 ctx: this,
582 value: row,
583 };
584 let mut pk_nonce = [b8(0); 16];
585 for i in 0..16 {
586 pk_nonce[i] = cur[34 + i];
587 }
588 nonce_write = NonceWrite {
589 valid: true,
590 ctx: this,
591 value: pk_nonce,
592 };
593 for i in 0..14 {
594 data.nonce[i] = cur[50 + i];
595 }
596 }
597 if beat == b5(3) {
598 data.nonce[14] = cur[0];
599 data.nonce[15] = cur[1];
600 let mut row = [b8(0); 64];
601 for i in 0..32 {
602 row[i] = cur[2 + i];
603 row[32 + i] = q.data.pk_hash[i];
604 }
605 header = HeaderWrite {
606 enable: true,
607 addr: (this.resize::<9>() << 3) | b9(1),
608 value: row,
609 };
610 }
611 if beat == b5(4) || beat == b5(5) || beat == b5(6) {
612 let mut row = [b8(0); 64];
613 for i in 0..64 {
614 row[i] = window[34 + i];
615 }
616 header = HeaderWrite {
617 enable: true,
618 addr: (this.resize::<9>() << 3) | (beat - b5(2)).resize::<9>(),
619 value: row,
620 };
621 }
622 if beat == b5(MESSAGE_BEAT as u128) {
623 let mut row = [b8(0); 64];
624 for i in 0..32 {
625 row[i] = q.data.pk_hash[i];
626 }
627 for i in 0..16 {
628 row[32 + i] = q.data.nonce[i];
629 row[48 + i] = cur[42 + i];
630 }
631 header = HeaderWrite {
632 enable: true,
633 addr: this.resize::<9>() << 3,
634 value: row,
635 };
636 if !TWO_BLOCK {
637 loaded = Loaded {
638 valid: true,
639 ctx: this,
640 index: q.data.index,
641 };
642 }
643 }
644 if TWO_BLOCK && beat == b5(LAST_BEAT as u128) {
645 let mut row = [b8(0); 64];
647 for i in 0..48 {
648 row[i] = window[58 + i];
649 }
650 header = HeaderWrite {
651 enable: true,
652 addr: (this.resize::<9>() << 3) | b9(5),
653 value: row,
654 };
655 loaded = Loaded {
656 valid: true,
657 ctx: this,
658 index: q.data.index,
659 };
660 }
661 let mut onehot = [false; RECORD_BEATS];
663 for b in 0..RECORD_BEATS {
664 onehot[b] = beat == b5(b as u128);
665 }
666 for m in 0..NODE_BANKS {
667 let mut value = [b8(0); ELEMENT_BYTES];
668 let mut enable = false;
669 let mut slot = b4(0);
670 for b in 0..RECORD_BEATS {
671 if onehot[b] && NODE_HAS[b][m] {
672 enable = true;
673 slot = NODE_SLOT[b][m];
674 for y in 0..ELEMENT_BYTES {
675 value[y] = window[NODE_OFFSET[b][m] + y];
676 }
677 }
678 }
679 nodes[m] = NodeWrite {
680 enable,
681 addr: (this.resize::<10>() << 4) | slot.resize::<10>(),
682 value,
683 };
684 }
685 }
686
687 let answer = q.certs.answer;
689 let mut format_bad = c.format_bad;
690 let mut cert_known = q.control.cert_known;
691 let mut cert_pending = q.control.cert_pending;
692 let mut cert_entry = q.control.cert_entry;
693 let mut cert_ok = q.control.cert_ok;
694 for x in 0..CONTEXTS {
695 let here = b6(x as u128);
696 if answer.valid && answer.ctx == here {
697 cert_known[x] = true;
698 cert_pending[x] = answer.pending;
699 cert_entry[x] = answer.entry;
700 cert_ok[x] = answer.ok;
701 }
702 if retire && head == here {
703 cert_known[x] = false;
704 format_bad[x] = false;
705 }
706 }
707 c.format_bad = format_bad;
708 let start = take && beat == b5(0);
710 c.occupancy = q.control.occupancy + (if start { b7(1) } else { b7(0) }) - (if retire { b7(1) } else { b7(0) });
711 c.backlog = q.control.backlog + (if retire { b13(1) } else { b13(0) }) - (if finalize { b13(1) } else { b13(0) });
712 c.freed_valid = retire;
713 c.freed_ctx = head;
714 c.oldest = head;
715 c.cert_known = cert_known;
716 c.cert_pending = cert_pending;
717 c.cert_entry = cert_entry;
718 c.cert_ok = cert_ok;
719
720 let d = VerifyCoreD::<TABLE_MASK> {
721 chains: ChainsInput {
722 nodes,
723 digits: q.hasher.digits,
724 nonce: nonce_write,
725 retire: Retire {
726 valid: q.control.freed_valid,
727 ctx: q.control.freed_ctx,
728 },
729 row: q.hasher.row,
730 },
731 hasher: HasherInput {
732 header,
733 check,
734 loaded,
735 chains_done: q.chains.done,
736 row: q.chains.row,
737 oldest: q.control.oldest,
738 retire_valid: q.control.freed_valid,
739 retire_ctx: q.control.freed_ctx,
740 },
741 certs: CertsInput {
742 commitment_valid: (take && beat == b5(0)) || warm_first,
743 signature_valid: (take && beat == b5(1)) || warm_second,
744 beat: if warm_first {
745 input.warm.commitment
746 } else if warm_second {
747 input.warm.signature
748 } else {
749 cur
750 },
751 ctx: if beat == b5(0) { q.control.started.resize() } else { ctx },
752 slot: if warm_first { input.warm.slot } else { input.slot },
753 warm: warm_first,
754 taken: input.cert_taken,
755 result: input.cert_result,
756 release_valid: finalize && waiting,
757 release_entry: wait_entry,
758 },
759 pending: RamIn::<b16, 12> {
760 read_addr: queue_read,
761 write: queue_write,
762 },
763 control: c,
764 data,
765 };
766 (
767 CoreOutput {
768 beat_ready: ready,
769 warm_taken: warm_second,
770 verdict: Verdict {
771 valid: finalize,
772 byte,
773 },
774 cert_request: q.certs.request,
775 retired: q.control.finalized,
776 valid: q.control.valid,
777 cert_checks: q.certs.checks,
778 cert_fails: q.certs.fails,
779 ready: q.chains.ready,
780 activity: Activity {
781 blake3: q.hasher.issued,
782 lanes: q.chains.stepped,
783 beat: take,
784 beat_stall: input.beat.valid && !ready,
785 },
786 },
787 d,
788 )
789}