1use rhdl::{core::ReplicatedSynchronous, prelude::*};
41use rhdl_fpga::core::dff::DFF;
42use rhdl_primitives::NoResetDff;
43use sha256_rhdl::lane::{ExternalFullCompressionLane, InlineCompressionLane};
44
45use crate::{
46 blocks::HashBytes,
47 cluster::{CompressionLaneComponent, LaneLocalBeat, LaneLocalCluster, LaneLocalClusterInput},
48 digits::MessageBytes,
49};
50
51pub const SIGNER_CLUSTER_COUNT: usize = 3;
53pub const SIGNER_FRAME_BEATS: usize = 35;
55
56const _: () = {
57 assert!(SIGNER_CLUSTER_COUNT == 3);
58 assert!(SIGNER_FRAME_BEATS == 35);
59};
60
61#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
63pub struct GlobalFrameSource {
64 pub valid: bool,
66 pub beat: LaneLocalBeat,
68}
69
70#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
72pub struct SignerOutputBeat {
73 pub beat: LaneLocalBeat,
75 pub cluster: b2,
77}
78
79#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
81pub struct GlobalBeatValidationInput {
82 pub beat: LaneLocalBeat,
84 pub expected_beat: b6,
86 pub expected_job_id: b32,
88 pub check_job_id: bool,
90}
91
92#[kernel]
98#[allow(clippy::comparison_chain, clippy::needless_range_loop)] pub fn global_beat_is_canonical_kernel(input: GlobalBeatValidationInput) -> bool {
100 let job_matches = !input.check_job_id || input.beat.job_id == input.expected_job_id;
101 let mut final_padding_zero = true;
102 for lane in 32..64 {
103 if input.beat.data[lane] != b8(0) {
104 final_padding_zero = false;
105 }
106 }
107 let control_matches = if input.expected_beat < b6(34) {
108 input.beat.keep == b64(0xffff_ffff_ffff_ffff) && !input.beat.last
109 } else if input.expected_beat == b6(34) {
110 input.beat.keep == b64(0xffff_ffff) && input.beat.last && final_padding_zero
111 } else {
112 false
113 };
114 job_matches && control_matches
115}
116
117#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
119pub struct GlobalLaneSelection {
120 pub valid: bool,
122 pub cluster: b2,
124 pub next_cursor: b2,
126}
127
128#[kernel]
130#[allow(clippy::assign_op_pattern)] pub fn select_global_lane_kernel(
132 eligible: [bool; SIGNER_CLUSTER_COUNT],
133 cursor: b2,
134) -> GlobalLaneSelection {
135 let normalized_cursor = if cursor < b2(3) { cursor } else { b2(0) };
136 let mut selection = GlobalLaneSelection {
137 next_cursor: normalized_cursor,
138 ..GlobalLaneSelection::default()
139 };
140 let mut scan = normalized_cursor;
141 for _offset in 0..SIGNER_CLUSTER_COUNT {
142 if !selection.valid && eligible[scan] {
143 selection.valid = true;
144 selection.cluster = scan;
145 selection.next_cursor = if scan == b2(2) { b2(0) } else { scan + b2(1) };
146 }
147 scan = if scan == b2(2) { b2(0) } else { scan + b2(1) };
148 }
149 selection
150}
151
152#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
154pub struct GlobalFrameMergeInput {
155 pub clusters: [GlobalFrameSource; SIGNER_CLUSTER_COUNT],
157 pub output_ready: bool,
159}
160
161#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
164pub struct GlobalFrameMergeOutput {
165 pub cluster_ready: [bool; SIGNER_CLUSTER_COUNT],
167 pub output_valid: bool,
169 pub output: SignerOutputBeat,
171 pub frame_active: bool,
173 pub frame_cluster: b2,
175 pub expected_beat: b6,
177 pub captured: bool,
179 pub captured_final: bool,
181 pub delivered_final: bool,
183 pub fault: bool,
185}
186
187#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
190pub struct GlobalFrameMergeControl {
191 pub cursor: b2,
193 pub frame_active: bool,
195 pub frame_cluster: b2,
197 pub expected_beat: b6,
199 pub output_valid: bool,
201 pub output_cluster: b2,
203 pub fault: bool,
205}
206
207#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
209pub struct GlobalFrameMerge {
210 control: DFF<GlobalFrameMergeControl>,
211 frame_job_id: NoResetDff<b32>,
212 output: NoResetDff<LaneLocalBeat>,
213}
214
215impl Default for GlobalFrameMerge {
216 fn default() -> Self {
217 Self {
218 control: DFF::new(GlobalFrameMergeControl::default()),
219 frame_job_id: NoResetDff::new(),
220 output: NoResetDff::new(),
221 }
222 }
223}
224
225impl SynchronousIO for GlobalFrameMerge {
226 type I = GlobalFrameMergeInput;
227 type O = GlobalFrameMergeOutput;
228 type Kernel = global_frame_merge_kernel;
229}
230
231#[kernel]
232fn select_frame_source_kernel(
233 sources: [GlobalFrameSource; SIGNER_CLUSTER_COUNT],
234 cluster: b2,
235) -> GlobalFrameSource {
236 match cluster {
237 Bits::<2>(0) => sources[0],
238 Bits::<2>(1) => sources[1],
239 _ => sources[2],
240 }
241}
242
243#[kernel]
245pub fn global_frame_merge_kernel(
246 clock_reset: ClockReset,
247 input: GlobalFrameMergeInput,
248 q: GlobalFrameMergeQ,
249) -> (GlobalFrameMergeOutput, GlobalFrameMergeD) {
250 let resetting = clock_reset.reset.any();
251 let mut control = q.control;
252 let mut frame_job_id = q.frame_job_id;
253 let mut output_beat = q.output;
254 let mut cluster_ready = [false; SIGNER_CLUSTER_COUNT];
255 let mut captured = false;
256 let mut captured_final = false;
257
258 let ownership_fault = q.control.cursor >= b2(3)
259 || (q.control.frame_active && q.control.frame_cluster >= b2(3))
260 || (q.control.frame_active && q.control.expected_beat >= b6(35))
261 || (q.control.output_valid && q.control.output_cluster >= b2(3));
262 let output_valid = !resetting && q.control.output_valid && !q.control.fault && !ownership_fault;
263 let output_handshake = output_valid && input.output_ready;
264 let delivered_final = output_handshake && q.output.last;
265 let output_capacity = !q.control.output_valid || output_handshake;
266 if output_handshake {
267 control.output_valid = false;
268 }
269
270 if !resetting && ownership_fault {
271 control.fault = true;
272 control.frame_active = false;
273 }
274
275 if !resetting && q.control.frame_active && !q.control.fault && !ownership_fault {
276 let source = select_frame_source_kernel(input.clusters, q.control.frame_cluster);
277 if source.valid {
278 let canonical = global_beat_is_canonical_kernel(GlobalBeatValidationInput {
279 beat: source.beat,
280 expected_beat: q.control.expected_beat,
281 expected_job_id: q.frame_job_id,
282 check_job_id: q.control.expected_beat != b6(0),
283 });
284 if !canonical {
285 control.fault = true;
286 control.frame_active = false;
287 } else if output_capacity {
288 cluster_ready[q.control.frame_cluster] = true;
289 output_beat = source.beat;
290 control.output_cluster = q.control.frame_cluster;
291 control.output_valid = true;
292 captured = true;
293 if q.control.expected_beat == b6(0) {
294 frame_job_id = source.beat.job_id;
295 }
296 if q.control.expected_beat == b6(34) {
297 captured_final = true;
298 control.frame_active = false;
299 control.expected_beat = b6(0);
300
301 let eligible = [
305 input.clusters[0].valid && q.control.frame_cluster != b2(0),
306 input.clusters[1].valid && q.control.frame_cluster != b2(1),
307 input.clusters[2].valid && q.control.frame_cluster != b2(2),
308 ];
309 let selection = select_global_lane_kernel(eligible, q.control.cursor);
310 if selection.valid {
311 control.frame_active = true;
312 control.frame_cluster = selection.cluster;
313 control.cursor = selection.next_cursor;
314 }
315 } else {
316 control.expected_beat = q.control.expected_beat + b6(1);
317 }
318 }
319 }
320 } else if !resetting && !q.control.fault && !ownership_fault {
321 let eligible = [
322 input.clusters[0].valid,
323 input.clusters[1].valid,
324 input.clusters[2].valid,
325 ];
326 let selection = select_global_lane_kernel(eligible, q.control.cursor);
327 if selection.valid {
328 control.frame_active = true;
329 control.frame_cluster = selection.cluster;
330 control.expected_beat = b6(0);
331 control.cursor = selection.next_cursor;
332 }
333 }
334
335 if resetting {
336 control = GlobalFrameMergeControl::default();
337 cluster_ready = [false; SIGNER_CLUSTER_COUNT];
338 captured = false;
339 captured_final = false;
340 }
341
342 let output = GlobalFrameMergeOutput {
343 cluster_ready,
344 output_valid,
345 output: SignerOutputBeat {
346 beat: q.output,
347 cluster: q.control.output_cluster,
348 },
349 frame_active: !resetting && q.control.frame_active,
350 frame_cluster: q.control.frame_cluster,
351 expected_beat: q.control.expected_beat,
352 captured,
353 captured_final,
354 delivered_final,
355 fault: !resetting && q.control.fault,
356 };
357 let d = GlobalFrameMergeD {
358 control,
359 frame_job_id,
360 output: output_beat,
361 };
362 (output, d)
363}
364
365#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
367pub struct GlobalErrorSource {
368 pub valid: bool,
370 pub job_id: b32,
372 pub context: b2,
374}
375
376#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
378pub struct SignerErrorCompletion {
379 pub job_id: b32,
381 pub cluster: b2,
383 pub context: b2,
385}
386
387#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
389pub struct GlobalErrorMergeInput {
390 pub clusters: [GlobalErrorSource; SIGNER_CLUSTER_COUNT],
392 pub error_ready: bool,
394}
395
396#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
399pub struct GlobalErrorMergeOutput {
400 pub cluster_ready: [bool; SIGNER_CLUSTER_COUNT],
402 pub error_valid: bool,
404 pub error: SignerErrorCompletion,
406 pub captured: bool,
408 pub delivered: bool,
410 pub fault: bool,
412}
413
414#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
416pub struct GlobalErrorMergeControl {
417 pub cursor: b2,
419 pub output_valid: bool,
421 pub output_cluster: b2,
423 pub output_context: b2,
425 pub fault: bool,
427}
428
429#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
431pub struct GlobalErrorMerge {
432 control: DFF<GlobalErrorMergeControl>,
433 job_id: NoResetDff<b32>,
434}
435
436impl Default for GlobalErrorMerge {
437 fn default() -> Self {
438 Self {
439 control: DFF::new(GlobalErrorMergeControl::default()),
440 job_id: NoResetDff::new(),
441 }
442 }
443}
444
445impl SynchronousIO for GlobalErrorMerge {
446 type I = GlobalErrorMergeInput;
447 type O = GlobalErrorMergeOutput;
448 type Kernel = global_error_merge_kernel;
449}
450
451#[kernel]
453pub fn global_error_merge_kernel(
454 clock_reset: ClockReset,
455 input: GlobalErrorMergeInput,
456 q: GlobalErrorMergeQ,
457) -> (GlobalErrorMergeOutput, GlobalErrorMergeD) {
458 let resetting = clock_reset.reset.any();
459 let mut control = q.control;
460 let mut job_id = q.job_id;
461 let mut cluster_ready = [false; SIGNER_CLUSTER_COUNT];
462 let mut captured = false;
463 let ownership_fault =
464 q.control.cursor >= b2(3) || (q.control.output_valid && q.control.output_cluster >= b2(3));
465 let error_valid = !resetting && q.control.output_valid && !q.control.fault && !ownership_fault;
466 let delivered = error_valid && input.error_ready;
467 let capacity = !q.control.output_valid || delivered;
468 if delivered {
469 control.output_valid = false;
470 }
471
472 if !resetting && ownership_fault {
473 control.fault = true;
474 control.output_valid = false;
475 }
476
477 if !resetting && !q.control.fault && !ownership_fault && capacity {
478 let eligible = [
479 input.clusters[0].valid,
480 input.clusters[1].valid,
481 input.clusters[2].valid,
482 ];
483 let selection = select_global_lane_kernel(eligible, q.control.cursor);
484 if selection.valid {
485 cluster_ready[selection.cluster] = true;
486 control.cursor = selection.next_cursor;
487 control.output_valid = true;
488 control.output_cluster = selection.cluster;
489 let selected = match selection.cluster {
490 Bits::<2>(0) => input.clusters[0],
491 Bits::<2>(1) => input.clusters[1],
492 _ => input.clusters[2],
493 };
494 control.output_context = selected.context;
495 job_id = selected.job_id;
496 captured = true;
497 }
498 }
499
500 if resetting {
501 control = GlobalErrorMergeControl::default();
502 cluster_ready = [false; SIGNER_CLUSTER_COUNT];
503 captured = false;
504 }
505
506 let output = GlobalErrorMergeOutput {
507 cluster_ready,
508 error_valid,
509 error: SignerErrorCompletion {
510 job_id: q.job_id,
511 cluster: q.control.output_cluster,
512 context: q.control.output_context,
513 },
514 captured,
515 delivered,
516 fault: !resetting && q.control.fault,
517 };
518 let d = GlobalErrorMergeD { control, job_id };
519 (output, d)
520}
521
522#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
524pub struct SignerTopInput {
525 pub start_valid: bool,
527 pub private_seed: HashBytes,
529 pub message: MessageBytes,
531 pub job_id: b32,
533 pub output_ready: bool,
535 pub error_ready: bool,
537}
538
539#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
548pub struct SignerTopOutput {
549 pub start_ready: bool,
551 pub start_accepted: bool,
553 pub start_cluster: b2,
555 pub cluster_start_ready: [bool; SIGNER_CLUSTER_COUNT],
557 pub cluster_context_active: [[bool; 4]; SIGNER_CLUSTER_COUNT],
559 pub cluster_fault: [bool; SIGNER_CLUSTER_COUNT],
561 pub cluster_credit_release: [bool; SIGNER_CLUSTER_COUNT],
563 pub output_valid: bool,
565 pub output: SignerOutputBeat,
570 pub delivered_final: bool,
572 pub error_valid: bool,
575 pub error: SignerErrorCompletion,
580 pub delivered_error: bool,
582 pub fault: bool,
584}
585
586#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
588pub struct SignerTopControl {
589 pub admission_cursor: b2,
591 pub fault: bool,
593}
594
595#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
597pub struct SignerTop<
598 const ROUNDS: usize,
599 const ADDRESS_BITS: usize,
600 L = InlineCompressionLane<ROUNDS, ADDRESS_BITS>,
601> where
602 rhdl::bits::W<ROUNDS>: BitWidth,
603 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
604 L: CompressionLaneComponent,
605{
606 clusters:
607 ReplicatedSynchronous<LaneLocalCluster<ROUNDS, ADDRESS_BITS, L>, SIGNER_CLUSTER_COUNT>,
608 frames: GlobalFrameMerge,
609 errors: GlobalErrorMerge,
610 control: DFF<SignerTopControl>,
611}
612
613impl<const ROUNDS: usize, const ADDRESS_BITS: usize, L> Default
614 for SignerTop<ROUNDS, ADDRESS_BITS, L>
615where
616 rhdl::bits::W<ROUNDS>: BitWidth,
617 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
618 L: CompressionLaneComponent + Default,
619{
620 fn default() -> Self {
621 Self {
622 clusters: ReplicatedSynchronous::new(LaneLocalCluster::default()),
623 frames: GlobalFrameMerge::default(),
624 errors: GlobalErrorMerge::default(),
625 control: DFF::new(SignerTopControl::default()),
626 }
627 }
628}
629
630impl<const ROUNDS: usize, const ADDRESS_BITS: usize, L> SynchronousIO
631 for SignerTop<ROUNDS, ADDRESS_BITS, L>
632where
633 rhdl::bits::W<ROUNDS>: BitWidth,
634 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
635 L: CompressionLaneComponent,
636{
637 type I = SignerTopInput;
638 type O = SignerTopOutput;
639 type Kernel = signer_top_with_lane_kernel<ROUNDS, ADDRESS_BITS, L>;
640}
641
642#[kernel]
644#[allow(clippy::needless_range_loop)] pub fn signer_top_with_lane_kernel<
646 const ROUNDS: usize,
647 const ADDRESS_BITS: usize,
648 L: CompressionLaneComponent,
649>(
650 clock_reset: ClockReset,
651 input: SignerTopInput,
652 q: SignerTopQ<ROUNDS, ADDRESS_BITS, L>,
653) -> (SignerTopOutput, SignerTopD<ROUNDS, ADDRESS_BITS, L>)
654where
655 rhdl::bits::W<ROUNDS>: BitWidth,
656 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
657{
658 let resetting = clock_reset.reset.any();
659 let cluster_start_ready = [
660 q.clusters[0].start_ready,
661 q.clusters[1].start_ready,
662 q.clusters[2].start_ready,
663 ];
664 let cluster_fault = [
665 q.clusters[0].fault,
666 q.clusters[1].fault,
667 q.clusters[2].fault,
668 ];
669 let observed_fault = cluster_fault[0]
670 || cluster_fault[1]
671 || cluster_fault[2]
672 || q.frames.fault
673 || q.errors.fault
674 || q.control.admission_cursor >= b2(3);
675 let fault = q.control.fault || observed_fault;
676 let admission = select_global_lane_kernel(cluster_start_ready, q.control.admission_cursor);
677 let start_ready = !resetting && !fault && admission.valid;
678 let start_accepted = start_ready && input.start_valid;
679
680 let mut control = q.control;
681 if start_accepted {
682 control.admission_cursor = admission.next_cursor;
683 }
684 if !resetting && observed_fault {
685 control.fault = true;
686 }
687 if resetting {
688 control = SignerTopControl::default();
689 }
690
691 let mut cluster_inputs = [LaneLocalClusterInput::default(); SIGNER_CLUSTER_COUNT];
692 for cluster in 0..SIGNER_CLUSTER_COUNT {
693 cluster_inputs[cluster].output_ready = !resetting && q.frames.cluster_ready[cluster];
694 cluster_inputs[cluster].error_ready = !resetting && q.errors.cluster_ready[cluster];
695 }
696 if start_accepted {
697 cluster_inputs[admission.cluster].start_valid = true;
698 cluster_inputs[admission.cluster].private_seed = input.private_seed;
699 cluster_inputs[admission.cluster].message = input.message;
700 cluster_inputs[admission.cluster].job_id = input.job_id;
701 }
702
703 let frame_sources = [
704 GlobalFrameSource {
705 valid: q.clusters[0].output_valid,
706 beat: q.clusters[0].output,
707 },
708 GlobalFrameSource {
709 valid: q.clusters[1].output_valid,
710 beat: q.clusters[1].output,
711 },
712 GlobalFrameSource {
713 valid: q.clusters[2].output_valid,
714 beat: q.clusters[2].output,
715 },
716 ];
717 let error_sources = [
718 GlobalErrorSource {
719 valid: q.clusters[0].error_valid,
720 job_id: q.clusters[0].error_job_id,
721 context: q.clusters[0].error_context,
722 },
723 GlobalErrorSource {
724 valid: q.clusters[1].error_valid,
725 job_id: q.clusters[1].error_job_id,
726 context: q.clusters[1].error_context,
727 },
728 GlobalErrorSource {
729 valid: q.clusters[2].error_valid,
730 job_id: q.clusters[2].error_job_id,
731 context: q.clusters[2].error_context,
732 },
733 ];
734
735 let output = SignerTopOutput {
736 start_ready,
737 start_accepted,
738 start_cluster: admission.cluster,
739 cluster_start_ready: if resetting || fault {
740 [false; SIGNER_CLUSTER_COUNT]
741 } else {
742 cluster_start_ready
743 },
744 cluster_context_active: [
745 q.clusters[0].context_active,
746 q.clusters[1].context_active,
747 q.clusters[2].context_active,
748 ],
749 cluster_fault: if resetting {
750 [false; SIGNER_CLUSTER_COUNT]
751 } else {
752 cluster_fault
753 },
754 cluster_credit_release: [
755 !resetting && q.clusters[0].output_credit_release,
756 !resetting && q.clusters[1].output_credit_release,
757 !resetting && q.clusters[2].output_credit_release,
758 ],
759 output_valid: !resetting && q.frames.output_valid,
760 output: q.frames.output,
761 delivered_final: !resetting && q.frames.delivered_final,
762 error_valid: !resetting && q.errors.error_valid,
763 error: q.errors.error,
764 delivered_error: !resetting && q.errors.delivered,
765 fault: !resetting && fault,
766 };
767 let d = SignerTopD::<ROUNDS, ADDRESS_BITS, L> {
768 clusters: cluster_inputs,
769 frames: GlobalFrameMergeInput {
770 clusters: frame_sources,
771 output_ready: input.output_ready,
772 },
773 errors: GlobalErrorMergeInput {
774 clusters: error_sources,
775 error_ready: input.error_ready,
776 },
777 control,
778 };
779 (output, d)
780}
781
782#[kernel]
788pub fn signer_top_kernel<const ROUNDS: usize, const ADDRESS_BITS: usize>(
789 clock_reset: ClockReset,
790 input: SignerTopInput,
791 q: SignerTopQ<ROUNDS, ADDRESS_BITS>,
792) -> (SignerTopOutput, SignerTopD<ROUNDS, ADDRESS_BITS>)
793where
794 rhdl::bits::W<ROUNDS>: BitWidth,
795 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
796{
797 signer_top_with_lane_kernel::<ROUNDS, ADDRESS_BITS, InlineCompressionLane<ROUNDS, ADDRESS_BITS>>(
798 clock_reset,
799 input,
800 q,
801 )
802}
803
804pub type Sha256SignerTop = SignerTop<64, 6>;
806
807pub type ModularSha256SignerTop = SignerTop<64, 6, ExternalFullCompressionLane>;
813
814impl<const ROUNDS: usize, const ADDRESS_BITS: usize, L> SignerTop<ROUNDS, ADDRESS_BITS, L>
815where
816 rhdl::bits::W<ROUNDS>: BitWidth,
817 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
818 L: CompressionLaneComponent,
819{
820 #[doc(hidden)]
828 #[must_use]
829 pub fn simulation_request_accepted(
830 state: &<Self as Synchronous>::S,
831 ) -> [[bool; 4]; SIGNER_CLUSTER_COUNT] {
832 [
833 state.0.clusters[0].request_accepted,
834 state.0.clusters[1].request_accepted,
835 state.0.clusters[2].request_accepted,
836 ]
837 }
838}
839
840#[cfg(test)]
841mod tests {
842 use super::*;
843 use crate::cluster::LaneLocalClusterOutput;
844
845 fn ready_cluster() -> LaneLocalClusterOutput {
846 LaneLocalClusterOutput {
847 start_ready: true,
848 ..LaneLocalClusterOutput::default()
849 }
850 }
851
852 fn ready_top_q() -> SignerTopQ<4, 2> {
853 SignerTopQ::<4, 2> {
854 clusters: [ready_cluster(); SIGNER_CLUSTER_COUNT],
855 frames: GlobalFrameMergeOutput::default(),
856 errors: GlobalErrorMergeOutput::default(),
857 control: SignerTopControl::default(),
858 }
859 }
860
861 #[test]
862 fn admissions_fail_closed_on_every_fault_source_until_shared_reset() {
863 let active_clock = clock_reset(clock(false), reset(false));
864 let reset_clock = clock_reset(clock(false), reset(true));
865 let offered = SignerTopInput {
866 start_valid: true,
867 ..SignerTopInput::default()
868 };
869
870 let (healthy, _) = signer_top_kernel::<4, 2>(active_clock, offered, ready_top_q());
871 assert!(healthy.start_ready);
872 assert!(healthy.start_accepted);
873
874 for fault_source in 0..6 {
875 let mut q = ready_top_q();
876 match fault_source {
877 0 => q.clusters[0].fault = true,
878 1 => q.clusters[1].fault = true,
879 2 => q.clusters[2].fault = true,
880 3 => q.frames.fault = true,
881 4 => q.errors.fault = true,
882 _ => q.control.admission_cursor = b2(3),
883 }
884 let (faulted, d) = signer_top_kernel::<4, 2>(active_clock, offered, q);
885 assert!(faulted.fault);
886 assert!(!faulted.start_ready);
887 assert!(!faulted.start_accepted);
888 assert_eq!(faulted.cluster_start_ready, [false; SIGNER_CLUSTER_COUNT]);
889 assert!(d.control.fault);
890 }
891
892 let mut sticky_q = ready_top_q();
893 sticky_q.control.fault = true;
894 let (sticky, sticky_d) = signer_top_kernel::<4, 2>(active_clock, offered, sticky_q);
895 assert!(sticky.fault);
896 assert!(!sticky.start_ready);
897 assert!(!sticky.start_accepted);
898 assert!(sticky_d.control.fault);
899
900 let (during_reset, reset_d) = signer_top_kernel::<4, 2>(reset_clock, offered, sticky_q);
901 assert!(!during_reset.fault);
902 assert!(!during_reset.start_ready);
903 assert!(!during_reset.start_accepted);
904 assert_eq!(reset_d.control, SignerTopControl::default());
905
906 let (recovered, _) = signer_top_kernel::<4, 2>(active_clock, offered, ready_top_q());
907 assert!(recovered.start_ready);
908 assert!(recovered.start_accepted);
909 assert!(!recovered.fault);
910 }
911
912 #[test]
913 fn error_and_data_ownership_are_independent_at_the_top_boundary() {
914 let mut q = ready_top_q();
915 let data = LaneLocalBeat {
916 data: [b8(0x5a); 64],
917 keep: b64(0xffff_ffff_ffff_ffff),
918 last: false,
919 job_id: b32(0x1234_5678),
920 };
921 q.frames.output_valid = true;
922 q.frames.output = SignerOutputBeat {
923 beat: data,
924 cluster: b2(1),
925 };
926 q.frames.frame_active = true;
927 q.frames.frame_cluster = b2(1);
928 q.frames.cluster_ready = [false, true, false];
929 q.errors.error_valid = true;
930 q.errors.error = SignerErrorCompletion {
931 job_id: b32(0xdead_beef),
932 cluster: b2(0),
933 context: b2(3),
934 };
935 q.errors.cluster_ready = [true, false, false];
936 q.errors.delivered = true;
937
938 let input = SignerTopInput {
939 output_ready: false,
940 error_ready: true,
941 ..SignerTopInput::default()
942 };
943 let (output, d) =
944 signer_top_kernel::<4, 2>(clock_reset(clock(false), reset(false)), input, q);
945 assert!(output.output_valid);
946 assert_eq!(output.output.beat, data);
947 assert_eq!(output.output.cluster, b2(1));
948 assert!(output.error_valid);
949 assert_eq!(output.error.job_id, b32(0xdead_beef));
950 assert_eq!(output.error.cluster, b2(0));
951 assert_eq!(output.error.context, b2(3));
952 assert!(output.delivered_error);
953
954 assert!(d.clusters[0].error_ready);
957 assert!(!d.clusters[0].output_ready);
958 assert!(d.clusters[1].output_ready);
959 assert!(!d.clusters[1].error_ready);
960 assert!(!d.clusters[2].output_ready);
961 assert!(!d.clusters[2].error_ready);
962 assert!(!d.frames.output_ready);
963 assert!(d.errors.error_ready);
964 }
965
966 #[test]
967 fn invalid_internal_merge_ownership_never_grants_or_delivers() {
968 let active_clock = clock_reset(clock(false), reset(false));
969 let frame_source = GlobalFrameSource {
970 valid: true,
971 beat: LaneLocalBeat {
972 data: [b8(0x33); 64],
973 keep: b64(0xffff_ffff_ffff_ffff),
974 last: false,
975 job_id: b32(0x1111_2222),
976 },
977 };
978 let frame_input = GlobalFrameMergeInput {
979 clusters: [frame_source; SIGNER_CLUSTER_COUNT],
980 output_ready: true,
981 };
982 for corruption in 0..4 {
983 let mut control = GlobalFrameMergeControl {
984 frame_active: true,
985 output_valid: true,
986 ..GlobalFrameMergeControl::default()
987 };
988 match corruption {
989 0 => control.cursor = b2(3),
990 1 => control.frame_cluster = b2(3),
991 2 => control.expected_beat = b6(35),
992 _ => control.output_cluster = b2(3),
993 }
994 let q = GlobalFrameMergeQ {
995 control,
996 frame_job_id: b32(0x1111_2222),
997 output: frame_source.beat,
998 };
999 let (output, d) = global_frame_merge_kernel(active_clock, frame_input, q);
1000 assert!(!output.output_valid);
1001 assert!(!output.delivered_final);
1002 assert!(!output.captured);
1003 assert_eq!(output.cluster_ready, [false; SIGNER_CLUSTER_COUNT]);
1004 assert!(d.control.fault);
1005 assert!(!d.control.frame_active);
1006 }
1007
1008 let error_input = GlobalErrorMergeInput {
1009 clusters: [GlobalErrorSource {
1010 valid: true,
1011 job_id: b32(0xaaaa_5555),
1012 context: b2(2),
1013 }; SIGNER_CLUSTER_COUNT],
1014 error_ready: true,
1015 };
1016 for invalid_output_owner in [false, true] {
1017 let q = GlobalErrorMergeQ {
1018 control: GlobalErrorMergeControl {
1019 cursor: if invalid_output_owner { b2(0) } else { b2(3) },
1020 output_valid: invalid_output_owner,
1021 output_cluster: if invalid_output_owner { b2(3) } else { b2(0) },
1022 ..GlobalErrorMergeControl::default()
1023 },
1024 job_id: b32(0xaaaa_5555),
1025 };
1026 let (output, d) = global_error_merge_kernel(active_clock, error_input, q);
1027 assert!(!output.error_valid);
1028 assert!(!output.delivered);
1029 assert!(!output.captured);
1030 assert_eq!(output.cluster_ready, [false; SIGNER_CLUSTER_COUNT]);
1031 assert!(d.control.fault);
1032 assert!(!d.control.output_valid);
1033 }
1034 }
1035}