1use rhdl::{core::ReplicatedSynchronous, prelude::*};
50use rhdl_fpga::core::dff::DFF;
51use rhdl_primitives::NoResetDff;
52use sha256_rhdl::lane::{ExternalFullCompressionLane, InlineCompressionLane};
53
54use crate::{
55 cluster::CompressionLaneComponent,
56 verify::{
57 cluster::{VerifierCluster, VerifyClusterInput, VerifyResult},
58 framing::VerifyStreamBeat,
59 sha::{VERIFY_ERROR_NONE, VERIFY_ERROR_TRANSPORT},
60 },
61};
62
63pub const VERIFY_TOP_CLUSTERS: usize = 3;
65pub const VERIFY_TOP_CONTEXTS: usize = VERIFY_TOP_CLUSTERS * 4;
67
68pub const VERIFY_TOP_DIAGNOSTIC_CLUSTER_0: u128 = 1 << 0;
70pub const VERIFY_TOP_DIAGNOSTIC_CLUSTER_1: u128 = 1 << 1;
72pub const VERIFY_TOP_DIAGNOSTIC_CLUSTER_2: u128 = 1 << 2;
74pub const VERIFY_TOP_DIAGNOSTIC_OWNERSHIP: u128 = 1 << 3;
76pub const VERIFY_TOP_DIAGNOSTIC_ADMISSION_CURSOR: u128 = 1 << 4;
78pub const VERIFY_TOP_DIAGNOSTIC_RESULT_CURSOR: u128 = 1 << 5;
80
81const _: () = {
82 assert!(VERIFY_TOP_CLUSTERS == 3);
83 assert!(VERIFY_TOP_CONTEXTS == 12);
84};
85
86#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
88pub struct VerifierTopInput {
89 pub stream_valid: bool,
91 pub stream: VerifyStreamBeat,
93 pub result_ready: bool,
95}
96
97#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
99pub struct VerifierTopSelection {
100 pub valid: bool,
102 pub cluster: b2,
104 pub next_cursor: b2,
106}
107
108#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
111pub struct VerifierTopControl {
112 pub admission_cursor: b2,
114 pub frame_active: bool,
116 pub frame_cluster: b2,
118 pub frame_context: b2,
120 pub frame_generation: b8,
122 pub frame_beat: b6,
124 pub result_cursor: b2,
126 pub result_valid: bool,
128 pub result_cluster: b2,
130 pub poison: bool,
132 pub drain_armed: bool,
134 pub diagnostics: b8,
136 pub origin_cluster_fault: b3,
138 pub origin_cluster_diagnostics: [b16; VERIFY_TOP_CLUSTERS],
140}
141
142#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
152pub struct VerifierTopOutput {
153 pub stream_ready: bool,
155 pub stream_accepted: bool,
157 pub stream_cluster: b2,
159 pub stream_context: b2,
161 pub stream_generation: b8,
163 pub frame_active: bool,
165 pub frame_cluster: b2,
167 pub frame_beat: b6,
169 pub frame_job_id: b32,
171 pub cluster_stream_ready: [bool; VERIFY_TOP_CLUSTERS],
173 pub cluster_frame_active: [bool; VERIFY_TOP_CLUSTERS],
175 pub cluster_context_loading: [[bool; 4]; VERIFY_TOP_CLUSTERS],
177 pub cluster_context_active: [[bool; 4]; VERIFY_TOP_CLUSTERS],
179 pub cluster_context_result_valid: [[bool; 4]; VERIFY_TOP_CLUSTERS],
181 pub cluster_fault: [bool; VERIFY_TOP_CLUSTERS],
183 pub cluster_diagnostics: [b16; VERIFY_TOP_CLUSTERS],
185 pub result_valid: bool,
188 pub result: VerifyResult,
193 pub result_cluster: b2,
196 pub result_transfer: bool,
198 pub fault: bool,
200 pub drain_armed: bool,
202 pub diagnostics: b8,
204 pub origin_cluster_fault: b3,
206 pub origin_cluster_diagnostics: [b16; VERIFY_TOP_CLUSTERS],
208}
209
210#[kernel]
212#[allow(clippy::assign_op_pattern)] pub fn select_verifier_top_cluster_kernel(
214 eligible: [bool; VERIFY_TOP_CLUSTERS],
215 cursor: b2,
216) -> VerifierTopSelection {
217 let normalized_cursor = if cursor < b2(3) { cursor } else { b2(0) };
218 let mut selection = VerifierTopSelection {
219 next_cursor: normalized_cursor,
220 ..VerifierTopSelection::default()
221 };
222 let mut scan = normalized_cursor;
223 for _offset in 0..VERIFY_TOP_CLUSTERS {
224 if !selection.valid && eligible[scan] {
225 selection.valid = true;
226 selection.cluster = scan;
227 selection.next_cursor = if scan == b2(2) { b2(0) } else { scan + b2(1) };
228 }
229 scan = if scan == b2(2) { b2(0) } else { scan + b2(1) };
230 }
231 selection
232}
233
234#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
236pub struct VerifierTop<L = InlineCompressionLane<64, 6>>
237where
238 L: CompressionLaneComponent,
239{
240 clusters: ReplicatedSynchronous<VerifierCluster<L>, VERIFY_TOP_CLUSTERS>,
241 control: DFF<VerifierTopControl>,
242 frame_job_id: NoResetDff<b32>,
243 result: NoResetDff<VerifyResult>,
244}
245
246impl<L> Default for VerifierTop<L>
247where
248 L: CompressionLaneComponent + Default,
249{
250 fn default() -> Self {
251 Self {
252 clusters: ReplicatedSynchronous::new(VerifierCluster::default()),
253 control: DFF::new(VerifierTopControl::default()),
254 frame_job_id: NoResetDff::new(),
255 result: NoResetDff::new(),
256 }
257 }
258}
259
260impl<L> SynchronousIO for VerifierTop<L>
261where
262 L: CompressionLaneComponent,
263{
264 type I = VerifierTopInput;
265 type O = VerifierTopOutput;
266 type Kernel = verifier_top_with_lane_kernel<L>;
267}
268
269#[kernel]
271#[allow(clippy::assign_op_pattern, clippy::needless_range_loop)] pub fn verifier_top_with_lane_kernel<L: CompressionLaneComponent>(
273 clock_reset: ClockReset,
274 input: VerifierTopInput,
275 q: VerifierTopQ<L>,
276) -> (VerifierTopOutput, VerifierTopD<L>) {
277 let resetting = clock_reset.reset.any();
278 let cluster_ready = [
279 q.clusters[0].stream_ready,
280 q.clusters[1].stream_ready,
281 q.clusters[2].stream_ready,
282 ];
283 let cluster_frames = [
284 q.clusters[0].frame_active,
285 q.clusters[1].frame_active,
286 q.clusters[2].frame_active,
287 ];
288 let cluster_fault = [
289 q.clusters[0].fault,
290 q.clusters[1].fault,
291 q.clusters[2].fault,
292 ];
293 let cluster_diagnostics = [
294 q.clusters[0].diagnostics,
295 q.clusters[1].diagnostics,
296 q.clusters[2].diagnostics,
297 ];
298
299 let owner_consistent = match q.control.frame_cluster {
300 Bits::<2>(0) => {
301 cluster_frames[0]
302 && !cluster_frames[1]
303 && !cluster_frames[2]
304 && q.clusters[0].stream_context == q.control.frame_context
305 && q.clusters[0].stream_generation == q.control.frame_generation
306 && q.clusters[0].frame_beat == q.control.frame_beat
307 && q.clusters[0].frame_job_id == q.frame_job_id
308 }
309 Bits::<2>(1) => {
310 !cluster_frames[0]
311 && cluster_frames[1]
312 && !cluster_frames[2]
313 && q.clusters[1].stream_context == q.control.frame_context
314 && q.clusters[1].stream_generation == q.control.frame_generation
315 && q.clusters[1].frame_beat == q.control.frame_beat
316 && q.clusters[1].frame_job_id == q.frame_job_id
317 }
318 Bits::<2>(2) => {
319 !cluster_frames[0]
320 && !cluster_frames[1]
321 && cluster_frames[2]
322 && q.clusters[2].stream_context == q.control.frame_context
323 && q.clusters[2].stream_generation == q.control.frame_generation
324 && q.clusters[2].frame_beat == q.control.frame_beat
325 && q.clusters[2].frame_job_id == q.frame_job_id
326 }
327 _ => false,
328 };
329 let any_cluster_frame = cluster_frames[0] || cluster_frames[1] || cluster_frames[2];
330 let ownership_fault = !q.control.poison
331 && ((q.control.frame_active && !owner_consistent)
332 || (!q.control.frame_active && any_cluster_frame));
333 let admission_cursor_fault = q.control.admission_cursor >= b2(3);
334 let result_cursor_fault = q.control.result_cursor >= b2(3);
335 let observed_cluster_fault = cluster_fault[0] || cluster_fault[1] || cluster_fault[2];
336
337 let mut diagnostic_now = b8(0);
338 let mut origin_cluster_fault = b3(0);
339 if cluster_fault[0] {
340 diagnostic_now = diagnostic_now | b8(VERIFY_TOP_DIAGNOSTIC_CLUSTER_0);
341 origin_cluster_fault = origin_cluster_fault | b3(1);
342 }
343 if cluster_fault[1] {
344 diagnostic_now = diagnostic_now | b8(VERIFY_TOP_DIAGNOSTIC_CLUSTER_1);
345 origin_cluster_fault = origin_cluster_fault | b3(2);
346 }
347 if cluster_fault[2] {
348 diagnostic_now = diagnostic_now | b8(VERIFY_TOP_DIAGNOSTIC_CLUSTER_2);
349 origin_cluster_fault = origin_cluster_fault | b3(4);
350 }
351 if ownership_fault {
352 diagnostic_now = diagnostic_now | b8(VERIFY_TOP_DIAGNOSTIC_OWNERSHIP);
353 }
354 if admission_cursor_fault {
355 diagnostic_now = diagnostic_now | b8(VERIFY_TOP_DIAGNOSTIC_ADMISSION_CURSOR);
356 }
357 if result_cursor_fault {
358 diagnostic_now = diagnostic_now | b8(VERIFY_TOP_DIAGNOSTIC_RESULT_CURSOR);
359 }
360 let fatal_now = !resetting
361 && !q.control.poison
362 && (observed_cluster_fault
363 || ownership_fault
364 || admission_cursor_fault
365 || result_cursor_fault);
366 let fault = q.control.poison || fatal_now;
367 let admission_fatal_now = !resetting
368 && !q.control.poison
369 && (ownership_fault || admission_cursor_fault || result_cursor_fault);
370
371 let admission_target = if q.control.admission_cursor < b2(3) {
376 q.control.admission_cursor
377 } else {
378 b2(0)
379 };
380 let selected_cluster = if q.control.frame_active {
381 q.control.frame_cluster
382 } else {
383 admission_target
384 };
385 let selected_stream_ready = match selected_cluster {
389 Bits::<2>(0) => q.clusters[0].stream_ready,
390 Bits::<2>(1) => q.clusters[1].stream_ready,
391 _ => q.clusters[2].stream_ready,
392 };
393 let selected_stream_context = match selected_cluster {
394 Bits::<2>(0) => q.clusters[0].stream_context,
395 Bits::<2>(1) => q.clusters[1].stream_context,
396 _ => q.clusters[2].stream_context,
397 };
398 let selected_stream_generation = match selected_cluster {
399 Bits::<2>(0) => q.clusters[0].stream_generation,
400 Bits::<2>(1) => q.clusters[1].stream_generation,
401 _ => q.clusters[2].stream_generation,
402 };
403 let stream_ready =
407 !resetting && !q.control.poison && !admission_fatal_now && selected_stream_ready;
408 let stream_offer =
415 !resetting && !q.control.poison && !admission_fatal_now && input.stream_valid;
416 let stream_accepted = stream_offer && selected_stream_ready;
417
418 let result_transfer = !resetting && !fatal_now && q.control.result_valid && input.result_ready;
419 let result_slot_available = !q.control.result_valid || result_transfer;
420 let poisoned_result_eligible = q.control.poison && q.control.drain_armed;
421 let result_eligible = [
422 q.clusters[0].result_valid && (!q.control.poison || poisoned_result_eligible),
423 q.clusters[1].result_valid && (!q.control.poison || poisoned_result_eligible),
424 q.clusters[2].result_valid && (!q.control.poison || poisoned_result_eligible),
425 ];
426 let result_selection =
427 select_verifier_top_cluster_kernel(result_eligible, q.control.result_cursor);
428 let capture_result =
429 !resetting && !fatal_now && result_slot_available && result_selection.valid;
430
431 let mut control = q.control;
432 let mut frame_job_id = q.frame_job_id;
433 let mut result = q.result;
434 let mut visible_result = q.result;
435 let abort_clean_result = !resetting
436 && (fatal_now || q.control.poison)
437 && q.control.result_valid
438 && q.result.error == b3(VERIFY_ERROR_NONE);
439 if abort_clean_result {
440 visible_result = VerifyResult {
441 job_id: q.result.job_id,
442 verified: false,
443 error: b3(VERIFY_ERROR_TRANSPORT),
444 };
445 result = visible_result;
446 }
447
448 if result_transfer {
449 control.result_valid = false;
450 }
451 if capture_result {
452 let child = match result_selection.cluster {
453 Bits::<2>(0) => q.clusters[0].result,
454 Bits::<2>(1) => q.clusters[1].result,
455 _ => q.clusters[2].result,
456 };
457 let captured_error = if q.control.poison && child.error == b3(VERIFY_ERROR_NONE) {
458 b3(VERIFY_ERROR_TRANSPORT)
459 } else {
460 child.error
461 };
462 result = VerifyResult {
463 job_id: child.job_id,
464 verified: captured_error == b3(VERIFY_ERROR_NONE) && child.verified,
465 error: captured_error,
466 };
467 control.result_valid = true;
468 control.result_cluster = result_selection.cluster;
469 control.result_cursor = result_selection.next_cursor;
470 }
471
472 if stream_accepted {
473 if q.control.frame_active {
474 if q.control.frame_beat == b6(34) {
475 control.frame_active = false;
476 control.frame_beat = b6(0);
477 } else {
478 control.frame_beat = q.control.frame_beat + b6(1);
479 }
480 } else {
481 control.frame_active = true;
482 control.frame_cluster = admission_target;
483 control.frame_context = selected_stream_context;
484 control.frame_generation = selected_stream_generation;
485 control.frame_beat = b6(1);
486 control.admission_cursor = if admission_target == b2(2) {
487 b2(0)
488 } else {
489 admission_target + b2(1)
490 };
491 frame_job_id = input.stream.job_id;
492 }
493 }
494
495 if q.control.poison && !q.control.drain_armed {
496 control.drain_armed = true;
497 }
498 if fatal_now {
499 control.poison = true;
500 control.drain_armed = false;
501 control.frame_active = false;
502 control.frame_beat = b6(0);
503 control.diagnostics = q.control.diagnostics | diagnostic_now;
504 control.origin_cluster_fault = origin_cluster_fault;
505 control.origin_cluster_diagnostics = cluster_diagnostics;
506 }
507 if resetting {
508 control = VerifierTopControl::default();
509 }
510
511 let cluster_external_abort = !resetting && q.control.poison;
517 let cluster_0_stream_valid = stream_offer && selected_cluster == b2(0);
518 let cluster_1_stream_valid = stream_offer && selected_cluster == b2(1);
519 let cluster_2_stream_valid = stream_offer && selected_cluster == b2(2);
520 let cluster_0_result_ready = capture_result && result_selection.cluster == b2(0);
521 let cluster_1_result_ready = capture_result && result_selection.cluster == b2(1);
522 let cluster_2_result_ready = capture_result && result_selection.cluster == b2(2);
523 let cluster_inputs = [
524 VerifyClusterInput {
525 stream_valid: cluster_0_stream_valid,
526 stream: input.stream,
527 result_ready: cluster_0_result_ready,
528 external_abort: cluster_external_abort,
529 },
530 VerifyClusterInput {
531 stream_valid: cluster_1_stream_valid,
532 stream: input.stream,
533 result_ready: cluster_1_result_ready,
534 external_abort: cluster_external_abort,
535 },
536 VerifyClusterInput {
537 stream_valid: cluster_2_stream_valid,
538 stream: input.stream,
539 result_ready: cluster_2_result_ready,
540 external_abort: cluster_external_abort,
541 },
542 ];
543
544 let output = VerifierTopOutput {
545 stream_ready,
546 stream_accepted,
547 stream_cluster: selected_cluster,
548 stream_context: selected_stream_context,
549 stream_generation: selected_stream_generation,
550 frame_active: !resetting && q.control.frame_active,
551 frame_cluster: q.control.frame_cluster,
552 frame_beat: q.control.frame_beat,
553 frame_job_id: q.frame_job_id,
554 cluster_stream_ready: if resetting || q.control.poison || admission_fatal_now {
555 [false; VERIFY_TOP_CLUSTERS]
556 } else {
557 cluster_ready
558 },
559 cluster_frame_active: if resetting {
560 [false; VERIFY_TOP_CLUSTERS]
561 } else {
562 cluster_frames
563 },
564 cluster_context_loading: if resetting {
565 [[false; 4]; VERIFY_TOP_CLUSTERS]
566 } else {
567 [
568 q.clusters[0].context_loading,
569 q.clusters[1].context_loading,
570 q.clusters[2].context_loading,
571 ]
572 },
573 cluster_context_active: if resetting {
574 [[false; 4]; VERIFY_TOP_CLUSTERS]
575 } else {
576 [
577 q.clusters[0].context_active,
578 q.clusters[1].context_active,
579 q.clusters[2].context_active,
580 ]
581 },
582 cluster_context_result_valid: if resetting {
583 [[false; 4]; VERIFY_TOP_CLUSTERS]
584 } else {
585 [
586 q.clusters[0].context_result_valid,
587 q.clusters[1].context_result_valid,
588 q.clusters[2].context_result_valid,
589 ]
590 },
591 cluster_fault: if resetting {
592 [false; VERIFY_TOP_CLUSTERS]
593 } else {
594 cluster_fault
595 },
596 cluster_diagnostics: if resetting {
597 [b16(0); VERIFY_TOP_CLUSTERS]
598 } else {
599 cluster_diagnostics
600 },
601 result_valid: !resetting && q.control.result_valid,
602 result: visible_result,
603 result_cluster: q.control.result_cluster,
604 result_transfer,
605 fault: !resetting && fault,
606 drain_armed: !resetting && q.control.drain_armed,
607 diagnostics: if resetting {
608 b8(0)
609 } else if fatal_now {
610 q.control.diagnostics | diagnostic_now
611 } else {
612 q.control.diagnostics
613 },
614 origin_cluster_fault: if resetting {
615 b3(0)
616 } else if fatal_now {
617 origin_cluster_fault
618 } else {
619 q.control.origin_cluster_fault
620 },
621 origin_cluster_diagnostics: if resetting {
622 [b16(0); VERIFY_TOP_CLUSTERS]
623 } else if fatal_now {
624 cluster_diagnostics
625 } else {
626 q.control.origin_cluster_diagnostics
627 },
628 };
629 let d = VerifierTopD::<L> {
630 clusters: cluster_inputs,
631 control,
632 frame_job_id,
633 result,
634 };
635 (output, d)
636}
637
638pub type Sha256VerifierTop = VerifierTop<InlineCompressionLane<64, 6>>;
640
641pub type ModularSha256VerifierTop = VerifierTop<ExternalFullCompressionLane>;
647
648#[kernel]
650pub fn sha256_verifier_top_kernel(
651 clock_reset: ClockReset,
652 input: VerifierTopInput,
653 q: VerifierTopQ<InlineCompressionLane<64, 6>>,
654) -> (
655 VerifierTopOutput,
656 VerifierTopD<InlineCompressionLane<64, 6>>,
657) {
658 verifier_top_with_lane_kernel::<InlineCompressionLane<64, 6>>(clock_reset, input, q)
659}
660
661impl<L> VerifierTop<L>
662where
663 L: CompressionLaneComponent,
664{
665 #[doc(hidden)]
670 #[must_use]
671 pub fn simulation_request_accepted(
672 state: &<Self as Synchronous>::S,
673 ) -> [[bool; 4]; VERIFY_TOP_CLUSTERS] {
674 [
675 state.0.clusters[0].request_accepted,
676 state.0.clusters[1].request_accepted,
677 state.0.clusters[2].request_accepted,
678 ]
679 }
680
681 #[doc(hidden)]
686 #[must_use]
687 pub fn simulation_response_routed(
688 state: &<Self as Synchronous>::S,
689 ) -> [[bool; 4]; VERIFY_TOP_CLUSTERS] {
690 [
691 state.0.clusters[0].response_routed,
692 state.0.clusters[1].response_routed,
693 state.0.clusters[2].response_routed,
694 ]
695 }
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701 use crate::verify::{cluster::VerifyClusterOutput, sha::VERIFY_ERROR_FRAME};
702
703 type NativeLane = InlineCompressionLane<64, 6>;
704
705 fn fatal_collision_q(result: VerifyResult) -> VerifierTopQ<NativeLane> {
706 let mut clusters = [VerifyClusterOutput::default(); VERIFY_TOP_CLUSTERS];
707 clusters[0].fault = true;
708 clusters[0].diagnostics = b16(0x0004);
709 clusters[1].result_valid = true;
710 clusters[1].result = VerifyResult {
711 job_id: b32(0x2222_0002),
712 verified: true,
713 error: b3(VERIFY_ERROR_NONE),
714 };
715 VerifierTopQ::<NativeLane> {
716 clusters,
717 control: VerifierTopControl {
718 result_valid: true,
719 result_cluster: b2(2),
720 ..VerifierTopControl::default()
721 },
722 frame_job_id: b32(0),
723 result,
724 }
725 }
726
727 #[test]
728 fn fatal_edge_converts_clean_top_slot_and_suppresses_transfer_and_child_capture() {
729 let retained = VerifyResult {
730 job_id: b32(0x1111_0001),
731 verified: true,
732 error: b3(VERIFY_ERROR_NONE),
733 };
734 let (output, d) = verifier_top_with_lane_kernel::<NativeLane>(
735 clock_reset(clock(false), reset(false)),
736 VerifierTopInput {
737 result_ready: true,
738 ..VerifierTopInput::default()
739 },
740 fatal_collision_q(retained),
741 );
742
743 assert!(output.fault);
744 assert!(output.result_valid);
745 assert!(!output.result_transfer);
746 assert_eq!(output.result.job_id, retained.job_id);
747 assert!(!output.result.verified);
748 assert_eq!(output.result.error, b3(VERIFY_ERROR_TRANSPORT));
749 assert_eq!(output.result_cluster, b2(2));
750 assert!(d.control.result_valid);
751 assert_eq!(d.result, output.result);
752 assert!(!d.clusters[0].result_ready);
753 assert!(!d.clusters[1].result_ready);
754 assert!(!d.clusters[2].result_ready);
755 }
756
757 #[test]
758 fn fatal_edge_preserves_retained_nonzero_error() {
759 let retained = VerifyResult {
760 job_id: b32(0x3333_0003),
761 verified: false,
762 error: b3(VERIFY_ERROR_FRAME),
763 };
764 let (output, d) = verifier_top_with_lane_kernel::<NativeLane>(
765 clock_reset(clock(false), reset(false)),
766 VerifierTopInput {
767 result_ready: true,
768 ..VerifierTopInput::default()
769 },
770 fatal_collision_q(retained),
771 );
772
773 assert!(output.fault);
774 assert!(output.result_valid);
775 assert!(!output.result_transfer);
776 assert_eq!(output.result, retained);
777 assert_eq!(d.result, retained);
778 assert!(d.control.result_valid);
779 assert!(!d.clusters[1].result_ready);
780 }
781
782 #[test]
783 fn beat_zero_offer_targets_registered_cursor_even_when_another_cluster_is_ready() {
784 let stream = VerifyStreamBeat {
785 job_id: b32(0x3030_0003),
786 ..VerifyStreamBeat::default()
787 };
788 let mut clusters = [VerifyClusterOutput::default(); VERIFY_TOP_CLUSTERS];
789 clusters[0].stream_ready = true;
790 clusters[1].stream_context = b2(3);
791 clusters[1].stream_generation = b8(9);
792 clusters[2].stream_ready = true;
793 let q = VerifierTopQ::<NativeLane> {
794 clusters,
795 control: VerifierTopControl {
796 admission_cursor: b2(1),
797 ..VerifierTopControl::default()
798 },
799 frame_job_id: b32(0),
800 result: VerifyResult::default(),
801 };
802 let input = VerifierTopInput {
803 stream_valid: true,
804 stream,
805 result_ready: false,
806 };
807
808 let (stalled, stalled_d) = verifier_top_with_lane_kernel::<NativeLane>(
809 clock_reset(clock(false), reset(false)),
810 input,
811 q,
812 );
813 assert_eq!(stalled.stream_cluster, b2(1));
814 assert!(!stalled.stream_ready);
815 assert!(!stalled.stream_accepted);
816 assert!(stalled_d.clusters[1].stream_valid);
817 assert_eq!(stalled_d.clusters[1].stream, stream);
818 assert!(!stalled_d.clusters[0].stream_valid);
819 assert!(!stalled_d.clusters[2].stream_valid);
820 assert!(!stalled_d.control.frame_active);
821 assert_eq!(stalled_d.control.admission_cursor, b2(1));
822
823 let mut ready_q = q;
824 ready_q.clusters[1].stream_ready = true;
825 let (accepted, accepted_d) = verifier_top_with_lane_kernel::<NativeLane>(
826 clock_reset(clock(false), reset(false)),
827 input,
828 ready_q,
829 );
830 assert_eq!(accepted.stream_cluster, b2(1));
831 assert!(accepted.stream_ready);
832 assert!(accepted.stream_accepted);
833 assert!(accepted_d.clusters[1].stream_valid);
834 assert_eq!(accepted_d.clusters[1].stream, stream);
835 assert!(accepted_d.control.frame_active);
836 assert_eq!(accepted_d.control.frame_cluster, b2(1));
837 assert_eq!(accepted_d.control.frame_context, b2(3));
838 assert_eq!(accepted_d.control.frame_generation, b8(9));
839 assert_eq!(accepted_d.control.frame_beat, b6(1));
840 assert_eq!(accepted_d.control.admission_cursor, b2(2));
841 assert_eq!(accepted_d.frame_job_id, stream.job_id);
842 }
843
844 #[test]
845 fn selected_child_offer_is_ready_independent_and_acceptance_matches() {
846 let stream = VerifyStreamBeat {
847 job_id: b32(0x4040_0004),
848 ..VerifyStreamBeat::default()
849 };
850 let mut clusters = [VerifyClusterOutput::default(); VERIFY_TOP_CLUSTERS];
851 clusters[1].frame_active = true;
852 clusters[1].frame_beat = b6(5);
853 clusters[1].frame_job_id = stream.job_id;
854 clusters[1].stream_context = b2(2);
855 clusters[1].stream_generation = b8(7);
856 let q = VerifierTopQ::<NativeLane> {
857 clusters,
858 control: VerifierTopControl {
859 frame_active: true,
860 frame_cluster: b2(1),
861 frame_context: b2(2),
862 frame_generation: b8(7),
863 frame_beat: b6(5),
864 ..VerifierTopControl::default()
865 },
866 frame_job_id: stream.job_id,
867 result: VerifyResult::default(),
868 };
869 let input = VerifierTopInput {
870 stream_valid: true,
871 stream,
872 result_ready: false,
873 };
874
875 let (stalled, stalled_d) = verifier_top_with_lane_kernel::<NativeLane>(
876 clock_reset(clock(false), reset(false)),
877 input,
878 q,
879 );
880 assert!(!stalled.stream_ready);
881 assert!(!stalled.stream_accepted);
882 assert_eq!(stalled.stream_cluster, b2(1));
883 assert_eq!(stalled.stream_context, b2(2));
884 assert_eq!(stalled.stream_generation, b8(7));
885 assert!(stalled_d.clusters[1].stream_valid);
886 assert_eq!(stalled_d.clusters[1].stream, stream);
887 assert!(!stalled_d.clusters[0].stream_valid);
888 assert!(!stalled_d.clusters[2].stream_valid);
889 assert_eq!(
890 stalled.stream_accepted,
891 q.clusters[1].stream_ready && stalled_d.clusters[1].stream_valid
892 );
893 assert_eq!(stalled_d.control.frame_beat, b6(5));
894
895 let mut ready_q = q;
896 ready_q.clusters[1].stream_ready = true;
897 let (accepted, accepted_d) = verifier_top_with_lane_kernel::<NativeLane>(
898 clock_reset(clock(false), reset(false)),
899 input,
900 ready_q,
901 );
902 assert!(accepted.stream_ready);
903 assert!(accepted.stream_accepted);
904 assert!(accepted_d.clusters[1].stream_valid);
905 assert_eq!(accepted_d.clusters[1].stream, stream);
906 assert_eq!(
907 accepted.stream_accepted,
908 ready_q.clusters[1].stream_ready && accepted_d.clusters[1].stream_valid
909 );
910 assert_eq!(accepted_d.control.frame_beat, b6(6));
911 }
912
913 #[test]
914 fn stream_offer_and_result_capture_coexist_on_independent_one_hot_fields() {
915 let stream = VerifyStreamBeat {
916 job_id: b32(0x4141_0004),
917 ..VerifyStreamBeat::default()
918 };
919 let captured = VerifyResult {
920 job_id: b32(0x4242_0004),
921 verified: true,
922 error: b3(VERIFY_ERROR_NONE),
923 };
924 let mut clusters = [VerifyClusterOutput::default(); VERIFY_TOP_CLUSTERS];
925 clusters[0].stream_ready = true;
926 clusters[0].stream_context = b2(1);
927 clusters[0].stream_generation = b8(5);
928 clusters[2].result_valid = true;
929 clusters[2].result = captured;
930 let (output, d) = verifier_top_with_lane_kernel::<NativeLane>(
931 clock_reset(clock(false), reset(false)),
932 VerifierTopInput {
933 stream_valid: true,
934 stream,
935 result_ready: false,
936 },
937 VerifierTopQ::<NativeLane> {
938 clusters,
939 control: VerifierTopControl {
940 admission_cursor: b2(0),
941 result_cursor: b2(2),
942 ..VerifierTopControl::default()
943 },
944 frame_job_id: b32(0),
945 result: VerifyResult::default(),
946 },
947 );
948
949 assert!(output.stream_accepted);
950 assert!(d.clusters[0].stream_valid);
951 assert!(!d.clusters[1].stream_valid);
952 assert!(!d.clusters[2].stream_valid);
953 assert!(!d.clusters[0].result_ready);
954 assert!(!d.clusters[1].result_ready);
955 assert!(d.clusters[2].result_ready);
956 for cluster in d.clusters {
957 assert_eq!(cluster.stream, stream);
958 }
959 assert!(d.control.result_valid);
960 assert_eq!(d.control.result_cluster, b2(2));
961 assert_eq!(d.result, captured);
962 }
963
964 #[test]
965 #[allow(clippy::too_many_lines)] fn cluster_fault_containment_edge_accepts_beat_zero_exactly_once_then_broadcasts_abort() {
967 let stream = VerifyStreamBeat {
968 job_id: b32(0x4444_0004),
969 ..VerifyStreamBeat::default()
970 };
971 let retained = VerifyResult {
972 job_id: b32(0x5555_0005),
973 verified: true,
974 error: b3(VERIFY_ERROR_NONE),
975 };
976 let mut clusters = [VerifyClusterOutput::default(); VERIFY_TOP_CLUSTERS];
977 clusters[0].fault = true;
978 clusters[0].diagnostics = b16(0x0020);
979 clusters[1].stream_ready = true;
980 clusters[1].stream_context = b2(2);
981 clusters[1].stream_generation = b8(7);
982 clusters[2].result_valid = true;
983 clusters[2].result = VerifyResult {
984 job_id: b32(0x5656_0005),
985 verified: true,
986 error: b3(VERIFY_ERROR_NONE),
987 };
988 let q = VerifierTopQ::<NativeLane> {
989 clusters,
990 control: VerifierTopControl {
991 admission_cursor: b2(1),
992 result_cursor: b2(2),
993 result_valid: true,
994 result_cluster: b2(2),
995 ..VerifierTopControl::default()
996 },
997 frame_job_id: b32(0),
998 result: retained,
999 };
1000 let input = VerifierTopInput {
1001 stream_valid: true,
1002 stream,
1003 result_ready: true,
1004 };
1005 let mut clean_q = q;
1006 clean_q.clusters[0].fault = false;
1007 clean_q.clusters[0].diagnostics = b16(0);
1008 let (_, clean_d) = verifier_top_with_lane_kernel::<NativeLane>(
1009 clock_reset(clock(false), reset(false)),
1010 input,
1011 clean_q,
1012 );
1013 let (containment, d) = verifier_top_with_lane_kernel::<NativeLane>(
1014 clock_reset(clock(false), reset(false)),
1015 input,
1016 q,
1017 );
1018
1019 assert!(containment.fault);
1020 assert!(containment.stream_ready);
1021 assert!(containment.stream_accepted);
1022 assert_eq!(containment.stream_cluster, b2(1));
1023 assert_eq!(containment.stream_context, b2(2));
1024 assert_eq!(containment.stream_generation, b8(7));
1025 assert!(!containment.result_transfer);
1026 assert_eq!(containment.result.job_id, retained.job_id);
1027 assert_eq!(containment.result.error, b3(VERIFY_ERROR_TRANSPORT));
1028 assert!(d.clusters[1].stream_valid);
1029 assert_eq!(d.clusters[1].stream, stream);
1030 assert!(!d.clusters[0].stream_valid);
1031 assert!(!d.clusters[2].stream_valid);
1032 for (cluster, (faulted, clean)) in
1033 d.clusters.iter().zip(clean_d.clusters.iter()).enumerate()
1034 {
1035 assert_eq!(
1036 faulted.stream_valid, clean.stream_valid,
1037 "current fault contaminated cluster {cluster} stream offer"
1038 );
1039 assert_eq!(faulted.stream, stream);
1040 }
1041 assert!(clean_d.clusters[2].result_ready);
1042 for faulted in d.clusters {
1043 assert!(!faulted.result_ready);
1044 }
1045 assert!(d.control.poison);
1046 assert!(!d.control.frame_active);
1047 assert_eq!(d.frame_job_id, stream.job_id);
1048
1049 let mut next_clusters = [VerifyClusterOutput::default(); VERIFY_TOP_CLUSTERS];
1050 for cluster in &mut next_clusters {
1051 cluster.stream_ready = true;
1052 }
1053 let (poisoned, poisoned_d) = verifier_top_with_lane_kernel::<NativeLane>(
1054 clock_reset(clock(false), reset(false)),
1055 VerifierTopInput {
1056 stream_valid: true,
1057 stream,
1058 result_ready: false,
1059 },
1060 VerifierTopQ::<NativeLane> {
1061 clusters: next_clusters,
1062 control: d.control,
1063 frame_job_id: d.frame_job_id,
1064 result: d.result,
1065 },
1066 );
1067 assert!(poisoned.fault);
1068 assert!(!poisoned.stream_ready);
1069 assert!(!poisoned.stream_accepted);
1070 for cluster in 0..VERIFY_TOP_CLUSTERS {
1071 assert!(poisoned_d.clusters[cluster].external_abort);
1072 assert!(!poisoned_d.clusters[cluster].stream_valid);
1073 assert!(!poisoned_d.clusters[cluster].result_ready);
1074 }
1075 }
1076
1077 fn poisoned_drain_capture(
1078 error: b3,
1079 verified: bool,
1080 ) -> (VerifyResult, VerifierTopD<NativeLane>) {
1081 let mut clusters = [VerifyClusterOutput::default(); VERIFY_TOP_CLUSTERS];
1082 clusters[1].result_valid = true;
1083 clusters[1].result = VerifyResult {
1084 job_id: b32(0x6666_0006),
1085 verified,
1086 error,
1087 };
1088 let (output, d) = verifier_top_with_lane_kernel::<NativeLane>(
1089 clock_reset(clock(false), reset(false)),
1090 VerifierTopInput::default(),
1091 VerifierTopQ::<NativeLane> {
1092 clusters,
1093 control: VerifierTopControl {
1094 result_cursor: b2(1),
1095 poison: true,
1096 drain_armed: true,
1097 ..VerifierTopControl::default()
1098 },
1099 frame_job_id: b32(0),
1100 result: VerifyResult::default(),
1101 },
1102 );
1103 assert!(output.fault);
1104 assert!(!output.result_valid);
1105 assert!(d.control.result_valid);
1106 assert_eq!(d.control.result_cluster, b2(1));
1107 assert!(d.clusters[1].result_ready);
1108 assert!(!d.clusters[0].result_ready);
1109 assert!(!d.clusters[2].result_ready);
1110 (d.result, d)
1111 }
1112
1113 #[test]
1114 fn poisoned_drain_grant_is_payload_independent_and_capture_fails_closed() {
1115 let (clean, _) = poisoned_drain_capture(b3(VERIFY_ERROR_NONE), true);
1116 assert_eq!(clean.job_id, b32(0x6666_0006));
1117 assert!(!clean.verified);
1118 assert_eq!(clean.error, b3(VERIFY_ERROR_TRANSPORT));
1119
1120 let (frame, _) = poisoned_drain_capture(b3(VERIFY_ERROR_FRAME), false);
1121 assert_eq!(frame.job_id, b32(0x6666_0006));
1122 assert!(!frame.verified);
1123 assert_eq!(frame.error, b3(VERIFY_ERROR_FRAME));
1124 }
1125}