1use rhdl::{core::circuit::descriptor::SyncKind, prelude::*};
26use rhdl_fpga::core::dff::DFF;
27use sha256_rhdl::lane::{ExternalFullCompressionLane, InlineCompressionLane};
28
29use crate::{
30 CompressionLaneComponent, HashBytes, LANE_LOCAL_CONTEXTS, MessageBytes, SIGNER_CLUSTER_COUNT,
31 SIGNER_FRAME_BEATS, SignerOutputBeat, SignerTop, SignerTopInput,
32 global_beat_is_canonical_kernel,
33};
34
35pub const BENCHMARK_NONCE_WORDS: usize = 6;
37pub const BENCHMARK_CHECKSUM_WORDS: usize = 16;
39pub const BENCHMARK_LIVE_JOB_SLOTS: usize = SIGNER_CLUSTER_COUNT * LANE_LOCAL_CONTEXTS;
41pub const BENCHMARK_AXI_ADDR_BITS: usize = 12;
43pub const BENCHMARK_AXI_RANGE_BYTES: u16 = 4096;
45pub const BENCHMARK_ABI_VERSION: u32 = 0x0001_0000;
47
48pub const BENCHMARK_SIGNER_TOP_MODULE: &str = "wots_sha256_signer_top";
54
55pub trait BenchmarkSignerComponent:
62 Synchronous<I = SignerTopInput, O = crate::SignerTopOutput>
63 + Clone
64 + Copy
65 + Default
66 + PartialEq
67 + core::fmt::Debug
68{
69}
70
71impl<T> BenchmarkSignerComponent for T where
72 T: Synchronous<I = SignerTopInput, O = crate::SignerTopOutput>
73 + Clone
74 + Copy
75 + Default
76 + PartialEq
77 + core::fmt::Debug
78{
79}
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub struct InlineBenchmarkSigner<
87 const ROUNDS: usize,
88 const ADDRESS_BITS: usize,
89 L = InlineCompressionLane<ROUNDS, ADDRESS_BITS>,
90>(core::marker::PhantomData<L>)
91where
92 rhdl::bits::W<ROUNDS>: BitWidth,
93 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
94 L: CompressionLaneComponent + Default;
95
96impl<const ROUNDS: usize, const ADDRESS_BITS: usize, L> Default
97 for InlineBenchmarkSigner<ROUNDS, ADDRESS_BITS, L>
98where
99 rhdl::bits::W<ROUNDS>: BitWidth,
100 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
101 L: CompressionLaneComponent + Default,
102{
103 fn default() -> Self {
104 Self(core::marker::PhantomData)
105 }
106}
107
108impl<const ROUNDS: usize, const ADDRESS_BITS: usize, L> SynchronousDQ
109 for InlineBenchmarkSigner<ROUNDS, ADDRESS_BITS, L>
110where
111 rhdl::bits::W<ROUNDS>: BitWidth,
112 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
113 L: CompressionLaneComponent + Default,
114{
115 type D = <SignerTop<ROUNDS, ADDRESS_BITS, L> as SynchronousDQ>::D;
116 type Q = <SignerTop<ROUNDS, ADDRESS_BITS, L> as SynchronousDQ>::Q;
117}
118
119impl<const ROUNDS: usize, const ADDRESS_BITS: usize, L> SynchronousIO
120 for InlineBenchmarkSigner<ROUNDS, ADDRESS_BITS, L>
121where
122 rhdl::bits::W<ROUNDS>: BitWidth,
123 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
124 L: CompressionLaneComponent + Default,
125{
126 type I = SignerTopInput;
127 type O = crate::SignerTopOutput;
128 type Kernel = <SignerTop<ROUNDS, ADDRESS_BITS, L> as SynchronousIO>::Kernel;
129}
130
131impl<const ROUNDS: usize, const ADDRESS_BITS: usize, L> Synchronous
132 for InlineBenchmarkSigner<ROUNDS, ADDRESS_BITS, L>
133where
134 rhdl::bits::W<ROUNDS>: BitWidth,
135 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
136 L: CompressionLaneComponent + Default,
137{
138 type S = <SignerTop<ROUNDS, ADDRESS_BITS, L> as Synchronous>::S;
139
140 fn init(&self) -> Self::S {
141 SignerTop::<ROUNDS, ADDRESS_BITS, L>::default().init()
142 }
143
144 fn sim(&self, clock_reset: ClockReset, input: Self::I, state: &mut Self::S) -> Self::O {
145 SignerTop::<ROUNDS, ADDRESS_BITS, L>::default().sim(clock_reset, input, state)
146 }
147
148 fn descriptor(&self, scoped_name: ScopedName) -> Result<Descriptor<SyncKind>, RHDLError> {
149 SignerTop::<ROUNDS, ADDRESS_BITS, L>::default().descriptor(scoped_name)
150 }
151}
152
153#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
160pub struct ExternalModularSha256SignerTop;
161
162impl SynchronousDQ for ExternalModularSha256SignerTop {
163 type D = ();
164 type Q = ();
165}
166
167impl SynchronousIO for ExternalModularSha256SignerTop {
168 type I = SignerTopInput;
169 type O = crate::SignerTopOutput;
170 type Kernel = NoSynchronousKernel<ClockReset, SignerTopInput, (), (crate::SignerTopOutput, ())>;
171}
172
173impl Synchronous for ExternalModularSha256SignerTop {
174 type S = <SignerTop<64, 6, ExternalFullCompressionLane> as Synchronous>::S;
175
176 fn init(&self) -> Self::S {
177 SignerTop::<64, 6, ExternalFullCompressionLane>::default().init()
178 }
179
180 fn sim(&self, clock_reset: ClockReset, input: Self::I, state: &mut Self::S) -> Self::O {
181 SignerTop::<64, 6, ExternalFullCompressionLane>::default().sim(clock_reset, input, state)
182 }
183
184 fn descriptor(&self, scoped_name: ScopedName) -> Result<Descriptor<SyncKind>, RHDLError> {
185 Descriptor::<SyncKind> {
186 name: scoped_name,
187 input_kind: SignerTopInput::static_kind(),
188 output_kind: crate::SignerTopOutput::static_kind(),
189 d_kind: Kind::Empty,
190 q_kind: Kind::Empty,
191 kernel: None,
192 netlist: None,
193 hdl: Some(HDLDescriptor {
194 name: BENCHMARK_SIGNER_TOP_MODULE.into(),
195 modules: vlog::ModuleList { modules: vec![] },
196 }),
197 _phantom: core::marker::PhantomData,
198 }
199 .with_external_netlist_black_box()
200 }
201}
202
203pub const AXI_RESP_OKAY: u8 = 0;
205pub const AXI_RESP_SLVERR: u8 = 2;
207
208pub const BENCHMARK_REJECT_NONE: u8 = 0;
210pub const BENCHMARK_REJECT_ZERO_BATCH: u8 = 1;
212pub const BENCHMARK_REJECT_INDEX_OVERFLOW: u8 = 2;
214
215pub const BENCHMARK_FAULT_NONE: u8 = 0;
217pub const BENCHMARK_FAULT_SIGNER: u8 = 0x10;
219pub const BENCHMARK_FAULT_ADMISSION: u8 = 0x11;
221pub const BENCHMARK_FAULT_FRAME: u8 = 0x12;
223pub const BENCHMARK_FAULT_ERROR: u8 = 0x13;
225pub const BENCHMARK_FAULT_COUNTER: u8 = 0x14;
227
228pub const REG_AP_CTRL: u16 = 0x000;
230pub const REG_GIE: u16 = 0x004;
232pub const REG_IER: u16 = 0x008;
234pub const REG_ISR: u16 = 0x00c;
236pub const REG_BATCH: u16 = 0x010;
238pub const REG_ABI_VERSION: u16 = 0x014;
240pub const REG_NONCE_0: u16 = 0x018;
242pub const REG_BASE_LO: u16 = 0x030;
244pub const REG_BASE_HI: u16 = 0x034;
246pub const REG_RUN_BASE_LO: u16 = 0x038;
248pub const REG_RUN_BASE_HI: u16 = 0x03c;
250pub const REG_STATUS: u16 = 0x040;
252pub const REG_CODES: u16 = 0x044;
254pub const REG_ACCEPTED_LO: u16 = 0x048;
256pub const REG_COMPLETED_LO: u16 = 0x050;
258pub const REG_BEATS_LO: u16 = 0x058;
260pub const REG_ERRORS_LO: u16 = 0x060;
262pub const REG_ELAPSED_LO: u16 = 0x068;
264pub const REG_FIRST_COMPLETION_LO: u16 = 0x070;
266pub const REG_LAST_COMPLETION_LO: u16 = 0x078;
268pub const REG_COMPLETION_SPAN_LO: u16 = 0x080;
270pub const REG_PAYLOAD_CYCLES_LO: u16 = 0x088;
272pub const REG_CHECKSUM_0: u16 = 0x090;
274
275const _: () = {
276 assert!(SIGNER_FRAME_BEATS == 35);
277 assert!(BENCHMARK_NONCE_WORDS * 32 == 192);
278 assert!(BENCHMARK_CHECKSUM_WORDS * 32 == 512);
279 assert!(SIGNER_CLUSTER_COUNT == 3);
280 assert!(LANE_LOCAL_CONTEXTS == 4);
281 assert!(BENCHMARK_LIVE_JOB_SLOTS == 12);
282 assert!(BENCHMARK_AXI_RANGE_BYTES == 1 << BENCHMARK_AXI_ADDR_BITS);
283 assert!(REG_CHECKSUM_0 + 64 <= BENCHMARK_AXI_RANGE_BYTES);
284};
285
286#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
288pub struct BenchmarkEngineInput {
289 pub launch_valid: bool,
291 pub batch: b32,
293 pub nonce_prefix: [b32; BENCHMARK_NONCE_WORDS],
295 pub base_index: b64,
297}
298
299#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
302pub struct BenchmarkEngineOutput {
303 pub launch_ready: bool,
305 pub launch_accepted: bool,
307 pub launch_rejected: bool,
309 pub reject_code: b8,
311 pub busy: bool,
313 pub done: bool,
315 pub fault: bool,
317 pub fault_code: b8,
319 pub run_base_index: b64,
321 pub next_base_index: b64,
323 pub accepted: b64,
325 pub completed: b64,
327 pub beats: b64,
329 pub errors: b64,
331 pub elapsed_cycles: b64,
333 pub first_completion_cycle: b64,
335 pub last_completion_cycle: b64,
337 pub completion_span_cycles: b64,
339 pub payload_cycles: b64,
341 pub checksum: [b32; BENCHMARK_CHECKSUM_WORDS],
343}
344
345#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
347pub struct BenchmarkLaunchValidationInput {
348 pub batch: b32,
350 pub base_index: b64,
352}
353
354#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
356pub struct BenchmarkLaunchValidationOutput {
357 pub valid: bool,
359 pub next_base_index: b64,
361 pub reject_code: b8,
363}
364
365#[kernel]
367pub fn benchmark_launch_validation_kernel(
368 input: BenchmarkLaunchValidationInput,
369) -> BenchmarkLaunchValidationOutput {
370 let wide_base: b65 = input.base_index.resize();
371 let wide_batch: b65 = input.batch.resize();
372 let wide_next = wide_base + wide_batch;
373 let overflow = (wide_next >> 64) != b65(0);
374 let zero = input.batch == b32(0);
375 let valid = !zero && !overflow;
376 BenchmarkLaunchValidationOutput {
377 valid,
378 next_base_index: wide_next.resize(),
379 reject_code: if zero {
380 b8(1)
381 } else if overflow {
382 b8(2)
383 } else {
384 b8(0)
385 },
386 }
387}
388
389#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
391pub struct BenchmarkJobMaterial {
392 pub private_seed: HashBytes,
394 pub message: MessageBytes,
396}
397
398#[kernel]
405#[allow(clippy::needless_range_loop)] pub fn benchmark_job_material_kernel(
407 nonce_prefix: [b32; BENCHMARK_NONCE_WORDS],
408 index: b64,
409) -> BenchmarkJobMaterial {
410 let mut private_seed = [b8(0); 32];
411 for word in 0..BENCHMARK_NONCE_WORDS {
412 private_seed[word * 4] = nonce_prefix[word].resize();
413 private_seed[word * 4 + 1] = (nonce_prefix[word] >> 8).resize();
414 private_seed[word * 4 + 2] = (nonce_prefix[word] >> 16).resize();
415 private_seed[word * 4 + 3] = (nonce_prefix[word] >> 24).resize();
416 }
417 private_seed[24] = (index >> 56).resize();
418 private_seed[25] = (index >> 48).resize();
419 private_seed[26] = (index >> 40).resize();
420 private_seed[27] = (index >> 32).resize();
421 private_seed[28] = (index >> 24).resize();
422 private_seed[29] = (index >> 16).resize();
423 private_seed[30] = (index >> 8).resize();
424 private_seed[31] = index.resize();
425
426 let mut message = [b8(0); 32];
427 for lane in 0..32 {
428 message[lane] = private_seed[lane] ^ b8(0xa5);
429 }
430 BenchmarkJobMaterial {
431 private_seed,
432 message,
433 }
434}
435
436#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
438pub struct BenchmarkFrameValidationInput {
439 pub output: SignerOutputBeat,
441 pub frame_active: bool,
443 pub expected_beat: b6,
445 pub frame_job_id: b32,
447 pub frame_cluster: b2,
449 pub batch: b32,
451}
452
453#[kernel]
455pub fn benchmark_frame_is_canonical_kernel(input: BenchmarkFrameValidationInput) -> bool {
456 let continuity = !input.frame_active
457 || (input.output.beat.job_id == input.frame_job_id
458 && input.output.cluster == input.frame_cluster);
459 input.output.beat.job_id < input.batch
460 && input.output.cluster < b2(3)
461 && continuity
462 && global_beat_is_canonical_kernel(crate::GlobalBeatValidationInput {
463 beat: input.output.beat,
464 expected_beat: input.expected_beat,
465 expected_job_id: input.frame_job_id,
466 check_job_id: input.frame_active,
467 })
468}
469
470#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
472pub struct BenchmarkChecksumInput {
473 pub checksum: [b32; BENCHMARK_CHECKSUM_WORDS],
475 pub output: SignerOutputBeat,
477 pub beat_index: b6,
479}
480
481#[kernel]
486#[allow(clippy::needless_range_loop)] pub fn benchmark_checksum_kernel(input: BenchmarkChecksumInput) -> [b32; BENCHMARK_CHECKSUM_WORDS] {
488 let mut checksum = input.checksum;
489 let identity = input.output.beat.job_id
490 ^ input.beat_index.resize::<32>()
491 ^ input.output.cluster.resize::<32>();
492 for word in 0..BENCHMARK_CHECKSUM_WORDS {
493 let byte_zero: b32 = input.output.beat.data[word * 4].resize();
494 let byte_one: b32 = input.output.beat.data[word * 4 + 1].resize();
495 let byte_two: b32 = input.output.beat.data[word * 4 + 2].resize();
496 let byte_three: b32 = input.output.beat.data[word * 4 + 3].resize();
497 let packed = byte_zero | (byte_one << 8) | (byte_two << 16) | (byte_three << 24);
498 checksum[word] = input.checksum[word] ^ packed ^ identity;
499 }
500 checksum
501}
502
503#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
510pub struct BenchmarkLiveJob {
511 pub live: bool,
513 pub job_id: b32,
515 pub cluster: b2,
517 pub frame_started: bool,
519}
520
521#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
523pub struct BenchmarkScoreboardAuditInput {
524 pub live_jobs: [BenchmarkLiveJob; BENCHMARK_LIVE_JOB_SLOTS],
526}
527
528#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
530pub struct BenchmarkScoreboardAuditOutput {
531 pub valid: bool,
533 pub live_count: b4,
535 pub empty: bool,
537}
538
539#[kernel]
541fn benchmark_scoreboard_slot_cluster_kernel(slot: b4) -> b2 {
542 if slot < b4(4) {
543 b2(0)
544 } else if slot < b4(8) {
545 b2(1)
546 } else {
547 b2(2)
548 }
549}
550
551#[kernel]
553#[allow(clippy::assign_op_pattern, clippy::needless_range_loop)] pub fn benchmark_scoreboard_audit_kernel(
555 input: BenchmarkScoreboardAuditInput,
556) -> BenchmarkScoreboardAuditOutput {
557 let mut valid = true;
558 let mut live_count = b4(0);
559 for slot in 0..BENCHMARK_LIVE_JOB_SLOTS {
560 if input.live_jobs[slot].live {
561 live_count = live_count + b4(1);
562 let expected_cluster = benchmark_scoreboard_slot_cluster_kernel(b4(slot as u128));
563 if input.live_jobs[slot].cluster != expected_cluster {
564 valid = false;
565 }
566 }
567 }
568 BenchmarkScoreboardAuditOutput {
569 valid,
570 live_count,
571 empty: live_count == b4(0),
572 }
573}
574
575#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
577pub struct BenchmarkScoreboardAdmissionInput {
578 pub live_jobs: [BenchmarkLiveJob; BENCHMARK_LIVE_JOB_SLOTS],
580 pub job_id: b32,
582 pub cluster: b2,
584}
585
586#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
588pub struct BenchmarkScoreboardAdmissionOutput {
589 pub valid: bool,
591 pub slot: b4,
593}
594
595#[kernel]
597#[allow(clippy::needless_range_loop)] pub fn benchmark_scoreboard_admission_kernel(
599 input: BenchmarkScoreboardAdmissionInput,
600) -> BenchmarkScoreboardAdmissionOutput {
601 let mut partition_valid = true;
602 let mut duplicate = false;
603 let mut free_found = false;
604 let mut slot = b4(0);
605 for candidate in 0..BENCHMARK_LIVE_JOB_SLOTS {
606 let candidate_cluster = benchmark_scoreboard_slot_cluster_kernel(b4(candidate as u128));
607 if input.live_jobs[candidate].live
608 && input.live_jobs[candidate].cluster != candidate_cluster
609 {
610 partition_valid = false;
611 }
612 if input.live_jobs[candidate].live && input.live_jobs[candidate].job_id == input.job_id {
613 duplicate = true;
614 }
615 if !free_found && candidate_cluster == input.cluster && !input.live_jobs[candidate].live {
616 free_found = true;
617 slot = b4(candidate as u128);
618 }
619 }
620 BenchmarkScoreboardAdmissionOutput {
621 valid: partition_valid && input.cluster < b2(3) && !duplicate && free_found,
622 slot,
623 }
624}
625
626#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
628pub struct BenchmarkScoreboardLookupInput {
629 pub live_jobs: [BenchmarkLiveJob; BENCHMARK_LIVE_JOB_SLOTS],
631 pub job_id: b32,
633 pub cluster: b2,
635}
636
637#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
639pub struct BenchmarkScoreboardLookupOutput {
640 pub valid: bool,
642 pub slot: b4,
644 pub frame_started: bool,
646 pub match_count: b4,
648}
649
650#[kernel]
652#[allow(clippy::assign_op_pattern, clippy::needless_range_loop)] pub fn benchmark_scoreboard_lookup_kernel(
654 input: BenchmarkScoreboardLookupInput,
655) -> BenchmarkScoreboardLookupOutput {
656 let mut slot = b4(0);
657 let mut frame_started = false;
658 let mut match_count = b4(0);
659 let mut exact_count = b4(0);
660 for candidate in 0..BENCHMARK_LIVE_JOB_SLOTS {
661 if input.live_jobs[candidate].live && input.live_jobs[candidate].job_id == input.job_id {
662 match_count = match_count + b4(1);
663 let candidate_cluster = benchmark_scoreboard_slot_cluster_kernel(b4(candidate as u128));
664 if input.live_jobs[candidate].cluster == input.cluster
665 && input.live_jobs[candidate].cluster == candidate_cluster
666 {
667 slot = b4(candidate as u128);
668 frame_started = input.live_jobs[candidate].frame_started;
669 exact_count = exact_count + b4(1);
670 }
671 }
672 }
673 BenchmarkScoreboardLookupOutput {
674 valid: input.cluster < b2(3) && match_count == b4(1) && exact_count == b4(1),
675 slot,
676 frame_started,
677 match_count,
678 }
679}
680
681#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
683pub struct BenchmarkCounterAuditInput {
684 pub live_jobs: [BenchmarkLiveJob; BENCHMARK_LIVE_JOB_SLOTS],
686 pub accepted: b64,
688 pub completed: b64,
690 pub errors: b64,
692 pub beats: b64,
694 pub batch: b32,
696 pub frame_active: bool,
698}
699
700#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
702pub struct BenchmarkCounterAuditOutput {
703 pub valid: bool,
705 pub terminal_exact: bool,
707 pub live_count: b4,
709}
710
711#[kernel]
713pub fn benchmark_counter_audit_kernel(
714 input: BenchmarkCounterAuditInput,
715) -> BenchmarkCounterAuditOutput {
716 let scoreboard = benchmark_scoreboard_audit_kernel(BenchmarkScoreboardAuditInput {
717 live_jobs: input.live_jobs,
718 });
719 let terminal_total = input.completed + input.errors;
720 let live_count: b64 = scoreboard.live_count.resize();
721 let batch: b64 = input.batch.resize();
722 let expected_beats = (input.completed << 5) + (input.completed << 1) + input.completed;
723 let valid = scoreboard.valid
724 && input.accepted <= batch
725 && terminal_total <= input.accepted
726 && terminal_total + live_count == input.accepted;
727 BenchmarkCounterAuditOutput {
728 valid,
729 terminal_exact: valid
730 && terminal_total == batch
731 && input.accepted == batch
732 && scoreboard.empty
733 && !input.frame_active
734 && input.beats == expected_beats,
735 live_count: scoreboard.live_count,
736 }
737}
738
739#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
742pub struct BenchmarkEngineControl {
743 pub busy: bool,
745 pub fault: bool,
747 pub fault_code: b8,
749 pub accepted: b64,
751 pub completed: b64,
753 pub beats: b64,
755 pub errors: b64,
757 pub live_jobs: [BenchmarkLiveJob; BENCHMARK_LIVE_JOB_SLOTS],
759 pub elapsed_cycles: b64,
761 pub payload_started: bool,
763 pub first_payload_cycle: b64,
765 pub payload_cycles: b64,
767 pub completion_started: bool,
769 pub first_completion_cycle: b64,
771 pub last_completion_cycle: b64,
773 pub completion_span_cycles: b64,
775 pub frame_active: bool,
777 pub expected_beat: b6,
779 pub frame_job_id: b32,
781 pub frame_cluster: b2,
783}
784
785#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
787pub struct BenchmarkEngineData {
788 pub batch: b32,
790 pub nonce_prefix: [b32; BENCHMARK_NONCE_WORDS],
792 pub run_base_index: b64,
794 pub next_base_index: b64,
796 pub checksum: [b32; BENCHMARK_CHECKSUM_WORDS],
798}
799
800#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
802pub struct BenchmarkEngineWithSigner<S = InlineBenchmarkSigner<64, 6>>
803where
804 S: BenchmarkSignerComponent,
805{
806 signer: S,
807 control: DFF<BenchmarkEngineControl>,
808 data: DFF<BenchmarkEngineData>,
809}
810
811impl<S> Default for BenchmarkEngineWithSigner<S>
812where
813 S: BenchmarkSignerComponent,
814{
815 fn default() -> Self {
816 Self {
817 signer: S::default(),
818 control: DFF::new(BenchmarkEngineControl::default()),
819 data: DFF::new(BenchmarkEngineData::default()),
820 }
821 }
822}
823
824impl<S> SynchronousIO for BenchmarkEngineWithSigner<S>
825where
826 S: BenchmarkSignerComponent,
827{
828 type I = BenchmarkEngineInput;
829 type O = BenchmarkEngineOutput;
830 type Kernel = benchmark_engine_kernel<S>;
831}
832
833#[kernel]
835#[allow(clippy::if_not_else, clippy::needless_range_loop)] pub fn benchmark_engine_kernel<S: BenchmarkSignerComponent>(
837 clock_reset: ClockReset,
838 input: BenchmarkEngineInput,
839 q: BenchmarkEngineWithSignerQ<S>,
840) -> (BenchmarkEngineOutput, BenchmarkEngineWithSignerD<S>) {
841 let resetting = clock_reset.reset.any();
842 let launch = benchmark_launch_validation_kernel(BenchmarkLaunchValidationInput {
843 batch: input.batch,
844 base_index: input.base_index,
845 });
846 let launch_ready = !resetting && !q.control.busy && !q.control.fault && !q.signer.fault;
847 let launch_offered = launch_ready && input.launch_valid;
848 let launch_accepted = launch_offered && launch.valid;
849 let launch_rejected = launch_offered && !launch.valid;
850
851 let mut control = q.control;
852 let mut data = q.data;
853 let mut done = false;
854
855 if launch_accepted {
856 control = BenchmarkEngineControl {
857 busy: true,
858 ..BenchmarkEngineControl::default()
859 };
860 data = BenchmarkEngineData {
861 batch: input.batch,
862 nonce_prefix: input.nonce_prefix,
863 run_base_index: input.base_index,
864 next_base_index: launch.next_base_index,
865 checksum: [b32(0); BENCHMARK_CHECKSUM_WORDS],
866 };
867 }
868 if launch_rejected {
869 done = true;
870 }
871
872 let active = q.control.busy && !q.control.fault && !q.signer.fault;
873 let active_cycle = q.control.elapsed_cycles;
874 if active {
875 control.elapsed_cycles = q.control.elapsed_cycles + b64(1);
876 }
877
878 let current_index = q.data.run_base_index + q.control.accepted;
879 let material = benchmark_job_material_kernel(q.data.nonce_prefix, current_index);
880 let offer_job = active && q.control.accepted < q.data.batch.resize::<64>();
881 let signer_input = SignerTopInput {
882 start_valid: offer_job,
883 private_seed: material.private_seed,
884 message: material.message,
885 job_id: q.control.accepted.resize(),
886 output_ready: active,
887 error_ready: active,
888 };
889
890 let admitted_cluster_ready = match q.signer.start_cluster {
891 Bits::<2>(0) => q.signer.cluster_start_ready[0],
892 Bits::<2>(1) => q.signer.cluster_start_ready[1],
893 Bits::<2>(2) => q.signer.cluster_start_ready[2],
894 _ => false,
895 };
896 let admission = benchmark_scoreboard_admission_kernel(BenchmarkScoreboardAdmissionInput {
897 live_jobs: q.control.live_jobs,
898 job_id: q.control.accepted.resize(),
899 cluster: q.signer.start_cluster,
900 });
901 let admission_fault = q.signer.start_accepted
902 && (!active
903 || !offer_job
904 || !q.signer.start_ready
905 || !admitted_cluster_ready
906 || !admission.valid);
907 let admission_event_valid = q.signer.start_accepted && !admission_fault;
908 if admission_event_valid {
909 control.accepted = q.control.accepted + b64(1);
910 }
911
912 let beat_transfer = active && q.signer.output_valid;
913 let error_transfer = active && q.signer.error_valid;
914 let same_terminal_identity = beat_transfer
915 && error_transfer
916 && q.signer.output.beat.job_id == q.signer.error.job_id
917 && q.signer.output.cluster == q.signer.error.cluster;
918 let mut frame_fault = false;
919 let mut error_fault = false;
920 let frame_owner = benchmark_scoreboard_lookup_kernel(BenchmarkScoreboardLookupInput {
921 live_jobs: q.control.live_jobs,
922 job_id: q.signer.output.beat.job_id,
923 cluster: q.signer.output.cluster,
924 });
925 let error_owner = benchmark_scoreboard_lookup_kernel(BenchmarkScoreboardLookupInput {
926 live_jobs: q.control.live_jobs,
927 job_id: q.signer.error.job_id,
928 cluster: q.signer.error.cluster,
929 });
930 let final_beat = q.control.expected_beat == b6(34);
931 let frame_owner_phase = if q.control.frame_active {
932 frame_owner.frame_started
933 } else {
934 !frame_owner.frame_started && q.control.expected_beat == b6(0)
935 };
936 let mut frame_event_valid = false;
937 let mut error_event_valid = false;
938
939 if beat_transfer {
940 control.beats = q.control.beats + b64(1);
941 data.checksum = benchmark_checksum_kernel(BenchmarkChecksumInput {
942 checksum: q.data.checksum,
943 output: q.signer.output,
944 beat_index: q.control.expected_beat,
945 });
946 if !q.control.payload_started {
947 control.payload_started = true;
948 control.first_payload_cycle = active_cycle;
949 control.payload_cycles = b64(1);
950 } else {
951 control.payload_cycles = active_cycle - q.control.first_payload_cycle + b64(1);
952 }
953
954 let canonical = benchmark_frame_is_canonical_kernel(BenchmarkFrameValidationInput {
955 output: q.signer.output,
956 frame_active: q.control.frame_active,
957 expected_beat: q.control.expected_beat,
958 frame_job_id: q.control.frame_job_id,
959 frame_cluster: q.control.frame_cluster,
960 batch: q.data.batch,
961 });
962 frame_event_valid = canonical
963 && frame_owner.valid
964 && frame_owner_phase
965 && q.signer.delivered_final == final_beat;
966 frame_fault = !frame_event_valid;
967 if frame_event_valid {
968 if !q.control.frame_active {
969 control.frame_active = true;
970 control.frame_job_id = q.signer.output.beat.job_id;
971 control.frame_cluster = q.signer.output.cluster;
972 }
973 if final_beat {
974 control.frame_active = false;
975 control.expected_beat = b6(0);
976 control.completed = q.control.completed + b64(1);
977 if !q.control.completion_started {
978 control.completion_started = true;
979 control.first_completion_cycle = active_cycle;
980 control.last_completion_cycle = active_cycle;
981 control.completion_span_cycles = b64(0);
982 } else {
983 control.last_completion_cycle = active_cycle;
984 control.completion_span_cycles =
985 active_cycle - q.control.first_completion_cycle;
986 }
987 } else {
988 control.expected_beat = q.control.expected_beat + b6(1);
989 }
990 }
991 } else if q.signer.delivered_final {
992 frame_fault = true;
993 }
994
995 if error_transfer {
996 error_event_valid = q.signer.delivered_error
997 && error_owner.valid
998 && !error_owner.frame_started
999 && !same_terminal_identity
1000 && q.signer.error.job_id < q.data.batch
1001 && q.signer.error.cluster < b2(3);
1002 error_fault = !error_event_valid;
1003 if error_event_valid {
1004 control.errors = q.control.errors + b64(1);
1005 }
1006 } else if q.signer.delivered_error {
1007 error_fault = true;
1008 }
1009
1010 let mut live_jobs = q.control.live_jobs;
1011 if frame_event_valid {
1012 for slot in 0..BENCHMARK_LIVE_JOB_SLOTS {
1013 if b4(slot as u128) == frame_owner.slot {
1014 if final_beat {
1015 live_jobs[slot] = BenchmarkLiveJob::default();
1016 } else if !q.control.frame_active {
1017 live_jobs[slot].frame_started = true;
1018 }
1019 }
1020 }
1021 }
1022 if error_event_valid {
1023 for slot in 0..BENCHMARK_LIVE_JOB_SLOTS {
1024 if b4(slot as u128) == error_owner.slot {
1025 live_jobs[slot] = BenchmarkLiveJob::default();
1026 }
1027 }
1028 }
1029 if admission_event_valid {
1030 for slot in 0..BENCHMARK_LIVE_JOB_SLOTS {
1031 if b4(slot as u128) == admission.slot {
1032 live_jobs[slot] = BenchmarkLiveJob {
1033 live: true,
1034 job_id: q.control.accepted.resize(),
1035 cluster: q.signer.start_cluster,
1036 frame_started: false,
1037 };
1038 }
1039 }
1040 }
1041 control.live_jobs = live_jobs;
1042
1043 let counter_audit = benchmark_counter_audit_kernel(BenchmarkCounterAuditInput {
1044 live_jobs,
1045 accepted: control.accepted,
1046 completed: control.completed,
1047 errors: control.errors,
1048 beats: control.beats,
1049 batch: q.data.batch,
1050 frame_active: control.frame_active,
1051 });
1052 let terminal_total = control.completed + control.errors;
1053 let batch_total: b64 = q.data.batch.resize();
1054 let protocol_fault = q.signer.fault || admission_fault || frame_fault || error_fault;
1055 let counter_fault = active
1056 && !protocol_fault
1057 && (!counter_audit.valid
1058 || (terminal_total == batch_total && !counter_audit.terminal_exact));
1059 let observed_fault = protocol_fault || counter_fault;
1060 if active && !observed_fault && counter_audit.terminal_exact {
1061 control.busy = false;
1062 done = true;
1063 }
1064 if q.control.busy && observed_fault {
1065 control.fault = true;
1066 control.fault_code = if q.signer.fault {
1067 b8(0x10)
1068 } else if admission_fault {
1069 b8(0x11)
1070 } else if frame_fault {
1071 b8(0x12)
1072 } else if error_fault {
1073 b8(0x13)
1074 } else {
1075 b8(0x14)
1076 };
1077 control.busy = false;
1078 done = true;
1079 }
1080
1081 if resetting {
1082 control = BenchmarkEngineControl::default();
1083 data = BenchmarkEngineData::default();
1084 done = false;
1085 }
1086
1087 let output = BenchmarkEngineOutput {
1088 launch_ready,
1089 launch_accepted: !resetting && launch_accepted,
1090 launch_rejected: !resetting && launch_rejected,
1091 reject_code: if launch_rejected {
1092 launch.reject_code
1093 } else {
1094 b8(0)
1095 },
1096 busy: !resetting && control.busy,
1097 done: !resetting && done,
1098 fault: !resetting && control.fault,
1099 fault_code: control.fault_code,
1100 run_base_index: data.run_base_index,
1101 next_base_index: data.next_base_index,
1102 accepted: control.accepted,
1103 completed: control.completed,
1104 beats: control.beats,
1105 errors: control.errors,
1106 elapsed_cycles: control.elapsed_cycles,
1107 first_completion_cycle: control.first_completion_cycle,
1108 last_completion_cycle: control.last_completion_cycle,
1109 completion_span_cycles: control.completion_span_cycles,
1110 payload_cycles: control.payload_cycles,
1111 checksum: data.checksum,
1112 };
1113 let d = BenchmarkEngineWithSignerD::<S> {
1114 signer: signer_input,
1115 control,
1116 data,
1117 };
1118 (output, d)
1119}
1120
1121#[allow(type_alias_bounds)] pub type BenchmarkEngine<
1124 const ROUNDS: usize,
1125 const ADDRESS_BITS: usize,
1126 L: CompressionLaneComponent + Default = InlineCompressionLane<ROUNDS, ADDRESS_BITS>,
1127> where
1128 rhdl::bits::W<ROUNDS>: BitWidth,
1129 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
1130= BenchmarkEngineWithSigner<InlineBenchmarkSigner<ROUNDS, ADDRESS_BITS, L>>;
1131
1132pub type WotsSha256BenchmarkEngine = BenchmarkEngine<64, 6>;
1134pub type ModularWotsSha256BenchmarkEngine =
1136 BenchmarkEngineWithSigner<ExternalModularSha256SignerTop>;
1137
1138#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
1141pub struct AxiLiteBenchmarkInput {
1142 pub aw_valid: bool,
1144 pub aw_addr: b12,
1146 pub w_valid: bool,
1148 pub w_data: b32,
1150 pub w_strb: b4,
1152 pub b_ready: bool,
1154 pub ar_valid: bool,
1156 pub ar_addr: b12,
1158 pub r_ready: bool,
1160}
1161
1162#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
1165pub struct AxiLiteBenchmarkOutput {
1166 pub aw_ready: bool,
1168 pub w_ready: bool,
1170 pub b_valid: bool,
1172 pub b_resp: b2,
1174 pub ar_ready: bool,
1176 pub r_valid: bool,
1178 pub r_data: b32,
1180 pub r_resp: b2,
1182 pub interrupt: bool,
1184}
1185
1186#[allow(clippy::struct_excessive_bools)] #[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
1189pub struct AxiLiteBenchmarkControl {
1190 pub aw_pending: bool,
1192 pub aw_addr: b12,
1194 pub aw_locked_at_accept: bool,
1196 pub w_pending: bool,
1198 pub w_data: b32,
1200 pub w_strb: b4,
1202 pub w_locked_at_accept: bool,
1204 pub b_valid: bool,
1206 pub b_resp: b2,
1208 pub r_valid: bool,
1210 pub r_data: b32,
1212 pub r_resp: b2,
1214 pub start_pending: bool,
1216 pub done: bool,
1218 pub gie: bool,
1220 pub ier: b2,
1222 pub isr: b2,
1224 pub fault_seen: bool,
1226 pub last_reject_code: b8,
1228}
1229
1230#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
1232pub struct AxiLiteBenchmarkConfig {
1233 pub batch: b32,
1235 pub nonce_prefix: [b32; BENCHMARK_NONCE_WORDS],
1237 pub base_index: b64,
1239}
1240
1241#[kernel]
1243#[allow(clippy::assign_op_pattern)] pub fn benchmark_merge_write_kernel(old: b32, new: b32, strb: b4) -> b32 {
1245 let mut mask = b32(0);
1246 if strb & b4(1) != b4(0) {
1247 mask = mask | b32(0x0000_00ff);
1248 }
1249 if strb & b4(2) != b4(0) {
1250 mask = mask | b32(0x0000_ff00);
1251 }
1252 if strb & b4(4) != b4(0) {
1253 mask = mask | b32(0x00ff_0000);
1254 }
1255 if strb & b4(8) != b4(0) {
1256 mask = mask | b32(0xff00_0000);
1257 }
1258 (old & !mask) | (new & mask)
1259}
1260
1261#[kernel]
1263fn benchmark_counter_word_kernel(value: b64, high: bool) -> b32 {
1264 if high {
1265 (value >> 32).resize()
1266 } else {
1267 value.resize()
1268 }
1269}
1270
1271#[kernel]
1273fn benchmark_bool_word_kernel(value: bool) -> b32 {
1274 if value { b32(1) } else { b32(0) }
1275}
1276
1277#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
1279pub struct BenchmarkReadDecodeInput {
1280 pub address: b12,
1282 pub start_pending: bool,
1284 pub done: bool,
1286 pub gie: bool,
1288 pub ier: b2,
1290 pub isr: b2,
1292 pub config: AxiLiteBenchmarkConfig,
1294 pub last_reject_code: b8,
1296 pub engine: BenchmarkEngineOutput,
1298}
1299
1300#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
1302pub struct BenchmarkReadDecodeOutput {
1303 pub valid: bool,
1305 pub data: b32,
1307}
1308
1309#[kernel]
1311#[allow(clippy::too_many_lines)] pub fn benchmark_read_decode_kernel(input: BenchmarkReadDecodeInput) -> BenchmarkReadDecodeOutput {
1313 let mut output = BenchmarkReadDecodeOutput {
1314 valid: true,
1315 data: b32(0),
1316 };
1317 match input.address {
1318 Bits::<12>(0x000) => {
1319 output.data = benchmark_bool_word_kernel(input.start_pending)
1320 | (benchmark_bool_word_kernel(input.done) << 1)
1321 | (benchmark_bool_word_kernel(!input.engine.busy && !input.start_pending) << 2)
1322 | (benchmark_bool_word_kernel(input.engine.launch_ready && !input.start_pending)
1323 << 3);
1324 }
1325 Bits::<12>(0x004) => output.data = benchmark_bool_word_kernel(input.gie),
1326 Bits::<12>(0x008) => output.data = input.ier.resize(),
1327 Bits::<12>(0x00c) => output.data = input.isr.resize(),
1328 Bits::<12>(0x010) => output.data = input.config.batch,
1329 Bits::<12>(0x014) => output.data = b32(0x0001_0000),
1330 Bits::<12>(0x018) => output.data = input.config.nonce_prefix[0],
1331 Bits::<12>(0x01c) => output.data = input.config.nonce_prefix[1],
1332 Bits::<12>(0x020) => output.data = input.config.nonce_prefix[2],
1333 Bits::<12>(0x024) => output.data = input.config.nonce_prefix[3],
1334 Bits::<12>(0x028) => output.data = input.config.nonce_prefix[4],
1335 Bits::<12>(0x02c) => output.data = input.config.nonce_prefix[5],
1336 Bits::<12>(0x030) => {
1337 output.data = benchmark_counter_word_kernel(input.config.base_index, false);
1338 }
1339 Bits::<12>(0x034) => {
1340 output.data = benchmark_counter_word_kernel(input.config.base_index, true);
1341 }
1342 Bits::<12>(0x038) => {
1343 output.data = benchmark_counter_word_kernel(input.engine.run_base_index, false);
1344 }
1345 Bits::<12>(0x03c) => {
1346 output.data = benchmark_counter_word_kernel(input.engine.run_base_index, true);
1347 }
1348 Bits::<12>(0x040) => {
1349 output.data = benchmark_bool_word_kernel(input.engine.busy)
1350 | (benchmark_bool_word_kernel(input.engine.fault) << 1)
1351 | (benchmark_bool_word_kernel(input.last_reject_code != b8(0)) << 2)
1352 | (benchmark_bool_word_kernel(input.engine.fault_code == b8(0x10)) << 3)
1353 | (benchmark_bool_word_kernel(
1354 input.engine.fault_code != b8(0) && input.engine.fault_code != b8(0x10),
1355 ) << 4);
1356 }
1357 Bits::<12>(0x044) => {
1358 output.data = input.engine.fault_code.resize::<32>()
1359 | (input.last_reject_code.resize::<32>() << 8);
1360 }
1361 Bits::<12>(0x048) => {
1362 output.data = benchmark_counter_word_kernel(input.engine.accepted, false);
1363 }
1364 Bits::<12>(0x04c) => {
1365 output.data = benchmark_counter_word_kernel(input.engine.accepted, true);
1366 }
1367 Bits::<12>(0x050) => {
1368 output.data = benchmark_counter_word_kernel(input.engine.completed, false);
1369 }
1370 Bits::<12>(0x054) => {
1371 output.data = benchmark_counter_word_kernel(input.engine.completed, true);
1372 }
1373 Bits::<12>(0x058) => {
1374 output.data = benchmark_counter_word_kernel(input.engine.beats, false);
1375 }
1376 Bits::<12>(0x05c) => {
1377 output.data = benchmark_counter_word_kernel(input.engine.beats, true);
1378 }
1379 Bits::<12>(0x060) => {
1380 output.data = benchmark_counter_word_kernel(input.engine.errors, false);
1381 }
1382 Bits::<12>(0x064) => {
1383 output.data = benchmark_counter_word_kernel(input.engine.errors, true);
1384 }
1385 Bits::<12>(0x068) => {
1386 output.data = benchmark_counter_word_kernel(input.engine.elapsed_cycles, false);
1387 }
1388 Bits::<12>(0x06c) => {
1389 output.data = benchmark_counter_word_kernel(input.engine.elapsed_cycles, true);
1390 }
1391 Bits::<12>(0x070) => {
1392 output.data = benchmark_counter_word_kernel(input.engine.first_completion_cycle, false);
1393 }
1394 Bits::<12>(0x074) => {
1395 output.data = benchmark_counter_word_kernel(input.engine.first_completion_cycle, true);
1396 }
1397 Bits::<12>(0x078) => {
1398 output.data = benchmark_counter_word_kernel(input.engine.last_completion_cycle, false);
1399 }
1400 Bits::<12>(0x07c) => {
1401 output.data = benchmark_counter_word_kernel(input.engine.last_completion_cycle, true);
1402 }
1403 Bits::<12>(0x080) => {
1404 output.data = benchmark_counter_word_kernel(input.engine.completion_span_cycles, false);
1405 }
1406 Bits::<12>(0x084) => {
1407 output.data = benchmark_counter_word_kernel(input.engine.completion_span_cycles, true);
1408 }
1409 Bits::<12>(0x088) => {
1410 output.data = benchmark_counter_word_kernel(input.engine.payload_cycles, false);
1411 }
1412 Bits::<12>(0x08c) => {
1413 output.data = benchmark_counter_word_kernel(input.engine.payload_cycles, true);
1414 }
1415 Bits::<12>(0x090) => output.data = input.engine.checksum[0],
1416 Bits::<12>(0x094) => output.data = input.engine.checksum[1],
1417 Bits::<12>(0x098) => output.data = input.engine.checksum[2],
1418 Bits::<12>(0x09c) => output.data = input.engine.checksum[3],
1419 Bits::<12>(0x0a0) => output.data = input.engine.checksum[4],
1420 Bits::<12>(0x0a4) => output.data = input.engine.checksum[5],
1421 Bits::<12>(0x0a8) => output.data = input.engine.checksum[6],
1422 Bits::<12>(0x0ac) => output.data = input.engine.checksum[7],
1423 Bits::<12>(0x0b0) => output.data = input.engine.checksum[8],
1424 Bits::<12>(0x0b4) => output.data = input.engine.checksum[9],
1425 Bits::<12>(0x0b8) => output.data = input.engine.checksum[10],
1426 Bits::<12>(0x0bc) => output.data = input.engine.checksum[11],
1427 Bits::<12>(0x0c0) => output.data = input.engine.checksum[12],
1428 Bits::<12>(0x0c4) => output.data = input.engine.checksum[13],
1429 Bits::<12>(0x0c8) => output.data = input.engine.checksum[14],
1430 Bits::<12>(0x0cc) => output.data = input.engine.checksum[15],
1431 _ => output.valid = false,
1432 }
1433 output
1434}
1435
1436#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
1438pub struct AxiLiteBenchmarkKernelWithSigner<S = InlineBenchmarkSigner<64, 6>>
1439where
1440 S: BenchmarkSignerComponent,
1441{
1442 engine: BenchmarkEngineWithSigner<S>,
1443 control: DFF<AxiLiteBenchmarkControl>,
1444 config: DFF<AxiLiteBenchmarkConfig>,
1445}
1446
1447impl<S> Default for AxiLiteBenchmarkKernelWithSigner<S>
1448where
1449 S: BenchmarkSignerComponent,
1450{
1451 fn default() -> Self {
1452 Self {
1453 engine: BenchmarkEngineWithSigner::default(),
1454 control: DFF::new(AxiLiteBenchmarkControl::default()),
1455 config: DFF::new(AxiLiteBenchmarkConfig::default()),
1456 }
1457 }
1458}
1459
1460impl<S> SynchronousIO for AxiLiteBenchmarkKernelWithSigner<S>
1461where
1462 S: BenchmarkSignerComponent,
1463{
1464 type I = AxiLiteBenchmarkInput;
1465 type O = AxiLiteBenchmarkOutput;
1466 type Kernel = axi_lite_benchmark_kernel<S>;
1467}
1468
1469#[kernel]
1471#[allow(clippy::assign_op_pattern, clippy::too_many_lines)] pub fn axi_lite_benchmark_kernel<S: BenchmarkSignerComponent>(
1473 clock_reset: ClockReset,
1474 input: AxiLiteBenchmarkInput,
1475 q: AxiLiteBenchmarkKernelWithSignerQ<S>,
1476) -> (AxiLiteBenchmarkOutput, AxiLiteBenchmarkKernelWithSignerD<S>) {
1477 let resetting = clock_reset.reset.any();
1478 let aw_ready = !resetting && !q.control.aw_pending && !q.control.b_valid;
1479 let w_ready = !resetting && !q.control.w_pending && !q.control.b_valid;
1480 let ar_ready = !resetting && !q.control.r_valid;
1481 let mut control = q.control;
1482 let mut config = q.config;
1483
1484 if q.control.b_valid && input.b_ready {
1485 control.b_valid = false;
1486 }
1487 if q.control.r_valid && input.r_ready {
1488 control.r_valid = false;
1489 }
1490 if input.aw_valid && aw_ready {
1491 control.aw_pending = true;
1492 control.aw_addr = input.aw_addr;
1493 control.aw_locked_at_accept = q.engine.busy || q.control.start_pending;
1494 }
1495 if input.w_valid && w_ready {
1496 control.w_pending = true;
1497 control.w_data = input.w_data;
1498 control.w_strb = input.w_strb;
1499 control.w_locked_at_accept = q.engine.busy || q.control.start_pending;
1500 }
1501
1502 if q.control.aw_pending && q.control.w_pending && !q.control.b_valid {
1503 let locked = q.control.aw_locked_at_accept
1504 || q.control.w_locked_at_accept
1505 || q.engine.busy
1506 || q.control.start_pending;
1507 let mut write_valid = true;
1508 match q.control.aw_addr {
1509 Bits::<12>(0x000) => {
1510 if q.control.w_strb & b4(1) != b4(0) && q.control.w_data & b32(1) != b32(0) {
1511 if locked || !q.engine.launch_ready {
1512 write_valid = false;
1513 } else {
1514 control.start_pending = true;
1515 }
1516 }
1517 }
1518 Bits::<12>(0x004) => {
1519 if q.control.w_strb & b4(1) != b4(0) {
1520 control.gie = q.control.w_data & b32(1) != b32(0);
1521 }
1522 }
1523 Bits::<12>(0x008) => {
1524 if q.control.w_strb & b4(1) != b4(0) {
1525 control.ier = q.control.w_data.resize();
1526 }
1527 }
1528 Bits::<12>(0x00c) => {
1529 if q.control.w_strb & b4(1) != b4(0) {
1530 control.isr = q.control.isr ^ q.control.w_data.resize::<2>();
1531 }
1532 }
1533 Bits::<12>(0x010) => {
1534 if locked {
1535 write_valid = false;
1536 } else {
1537 config.batch = benchmark_merge_write_kernel(
1538 q.config.batch,
1539 q.control.w_data,
1540 q.control.w_strb,
1541 );
1542 }
1543 }
1544 Bits::<12>(0x018) => {
1545 if locked {
1546 write_valid = false;
1547 } else {
1548 config.nonce_prefix[0] = benchmark_merge_write_kernel(
1549 q.config.nonce_prefix[0],
1550 q.control.w_data,
1551 q.control.w_strb,
1552 );
1553 }
1554 }
1555 Bits::<12>(0x01c) => {
1556 if locked {
1557 write_valid = false;
1558 } else {
1559 config.nonce_prefix[1] = benchmark_merge_write_kernel(
1560 q.config.nonce_prefix[1],
1561 q.control.w_data,
1562 q.control.w_strb,
1563 );
1564 }
1565 }
1566 Bits::<12>(0x020) => {
1567 if locked {
1568 write_valid = false;
1569 } else {
1570 config.nonce_prefix[2] = benchmark_merge_write_kernel(
1571 q.config.nonce_prefix[2],
1572 q.control.w_data,
1573 q.control.w_strb,
1574 );
1575 }
1576 }
1577 Bits::<12>(0x024) => {
1578 if locked {
1579 write_valid = false;
1580 } else {
1581 config.nonce_prefix[3] = benchmark_merge_write_kernel(
1582 q.config.nonce_prefix[3],
1583 q.control.w_data,
1584 q.control.w_strb,
1585 );
1586 }
1587 }
1588 Bits::<12>(0x028) => {
1589 if locked {
1590 write_valid = false;
1591 } else {
1592 config.nonce_prefix[4] = benchmark_merge_write_kernel(
1593 q.config.nonce_prefix[4],
1594 q.control.w_data,
1595 q.control.w_strb,
1596 );
1597 }
1598 }
1599 Bits::<12>(0x02c) => {
1600 if locked {
1601 write_valid = false;
1602 } else {
1603 config.nonce_prefix[5] = benchmark_merge_write_kernel(
1604 q.config.nonce_prefix[5],
1605 q.control.w_data,
1606 q.control.w_strb,
1607 );
1608 }
1609 }
1610 Bits::<12>(0x030) => {
1611 if locked {
1612 write_valid = false;
1613 } else {
1614 let low = benchmark_merge_write_kernel(
1615 q.config.base_index.resize(),
1616 q.control.w_data,
1617 q.control.w_strb,
1618 );
1619 config.base_index =
1620 (q.config.base_index & b64(0xffff_ffff_0000_0000)) | low.resize::<64>();
1621 }
1622 }
1623 Bits::<12>(0x034) => {
1624 if locked {
1625 write_valid = false;
1626 } else {
1627 let old_high: b32 = (q.config.base_index >> 32).resize();
1628 let high =
1629 benchmark_merge_write_kernel(old_high, q.control.w_data, q.control.w_strb);
1630 config.base_index = (q.config.base_index & b64(0x0000_0000_ffff_ffff))
1631 | (high.resize::<64>() << 32);
1632 }
1633 }
1634 _ => write_valid = false,
1635 }
1636 control.aw_pending = false;
1637 control.aw_locked_at_accept = false;
1638 control.w_pending = false;
1639 control.w_locked_at_accept = false;
1640 control.b_valid = true;
1641 control.b_resp = if write_valid { b2(0) } else { b2(2) };
1642 }
1643
1644 if input.ar_valid && ar_ready {
1645 let decoded = benchmark_read_decode_kernel(BenchmarkReadDecodeInput {
1646 address: input.ar_addr,
1647 start_pending: q.control.start_pending,
1648 done: q.control.done,
1649 gie: q.control.gie,
1650 ier: q.control.ier,
1651 isr: q.control.isr,
1652 config: q.config,
1653 last_reject_code: q.control.last_reject_code,
1654 engine: q.engine,
1655 });
1656 control.r_valid = true;
1657 control.r_data = decoded.data;
1658 control.r_resp = if decoded.valid { b2(0) } else { b2(2) };
1659 if input.ar_addr == b12(0) {
1660 control.done = false;
1661 }
1662 }
1663
1664 if q.engine.launch_accepted {
1665 control.start_pending = false;
1666 control.last_reject_code = b8(0);
1667 config.base_index = q.engine.next_base_index;
1668 }
1669 if q.engine.launch_rejected {
1670 control.start_pending = false;
1671 control.last_reject_code = q.engine.reject_code;
1672 }
1673 if q.engine.done {
1674 control.done = true;
1675 control.isr = control.isr | b2(1);
1676 }
1677 if q.engine.fault && !q.control.fault_seen {
1678 control.fault_seen = true;
1679 control.isr = control.isr | b2(2);
1680 }
1681
1682 let engine_input = BenchmarkEngineInput {
1683 launch_valid: q.control.start_pending,
1684 batch: q.config.batch,
1685 nonce_prefix: q.config.nonce_prefix,
1686 base_index: q.config.base_index,
1687 };
1688
1689 if resetting {
1690 control = AxiLiteBenchmarkControl::default();
1691 config = AxiLiteBenchmarkConfig::default();
1692 }
1693
1694 let output = AxiLiteBenchmarkOutput {
1695 aw_ready,
1696 w_ready,
1697 b_valid: !resetting && q.control.b_valid,
1698 b_resp: q.control.b_resp,
1699 ar_ready,
1700 r_valid: !resetting && q.control.r_valid,
1701 r_data: q.control.r_data,
1702 r_resp: q.control.r_resp,
1703 interrupt: !resetting && q.control.gie && (q.control.isr & q.control.ier) != b2(0),
1704 };
1705 let d = AxiLiteBenchmarkKernelWithSignerD::<S> {
1706 engine: engine_input,
1707 control,
1708 config,
1709 };
1710 (output, d)
1711}
1712
1713#[allow(type_alias_bounds)] pub type AxiLiteBenchmarkKernel<
1716 const ROUNDS: usize,
1717 const ADDRESS_BITS: usize,
1718 L: CompressionLaneComponent + Default = InlineCompressionLane<ROUNDS, ADDRESS_BITS>,
1719> where
1720 rhdl::bits::W<ROUNDS>: BitWidth,
1721 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
1722= AxiLiteBenchmarkKernelWithSigner<InlineBenchmarkSigner<ROUNDS, ADDRESS_BITS, L>>;
1723
1724pub type WotsSha256AxiLiteBenchmarkKernel = AxiLiteBenchmarkKernel<64, 6>;
1726pub type ModularWotsSha256AxiLiteBenchmarkKernel =
1728 AxiLiteBenchmarkKernelWithSigner<ExternalModularSha256SignerTop>;
1729
1730#[cfg(test)]
1731mod tests {
1732 use super::*;
1733 use crate::LaneLocalBeat;
1734
1735 fn raw8(value: b8) -> u8 {
1736 value.raw().to_le_bytes()[0]
1737 }
1738
1739 fn raw4(value: b4) -> usize {
1740 usize::from(value.raw().to_le_bytes()[0] & 0x0f)
1741 }
1742
1743 fn raw32(value: b32) -> u32 {
1744 let bytes = value.raw().to_le_bytes();
1745 u32::from_le_bytes(bytes[..4].try_into().expect("four bytes"))
1746 }
1747
1748 #[test]
1749 fn external_signer_descriptor_is_typed_and_unresolved() -> Result<(), RHDLError> {
1750 let descriptor =
1751 ExternalModularSha256SignerTop.descriptor("benchmark_external_signer_probe".into())?;
1752 assert_eq!(
1753 descriptor.name.to_string(),
1754 "benchmark_external_signer_probe"
1755 );
1756 assert_eq!(descriptor.input_kind, SignerTopInput::static_kind());
1757 assert_eq!(
1758 descriptor.output_kind,
1759 crate::SignerTopOutput::static_kind()
1760 );
1761 assert!(descriptor.hdl().is_err());
1762 assert!(descriptor.hdl_unchecked()?.modules.modules.is_empty());
1763
1764 let wrapper = descriptor
1765 .netlist()?
1766 .as_vlog("benchmark_external_signer_probe")?
1767 .modules
1768 .pretty();
1769 assert!(wrapper.contains(&format!("{BENCHMARK_SIGNER_TOP_MODULE} bb_0(")));
1770 assert!(!wrapper.contains(&format!("module {BENCHMARK_SIGNER_TOP_MODULE}(")));
1771 Ok(())
1772 }
1773
1774 #[test]
1775 fn external_signer_simulation_delegates_to_the_exact_modular_signer() {
1776 let external = ExternalModularSha256SignerTop;
1777 let native = SignerTop::<64, 6, ExternalFullCompressionLane>::default();
1778 let mut external_state = external.init();
1779 let mut native_state = native.init();
1780 for clock_reset in [
1781 clock_reset(clock(false), reset(true)),
1782 clock_reset(clock(false), reset(false)),
1783 ] {
1784 let input = SignerTopInput::default();
1785 assert_eq!(
1786 external.sim(clock_reset, input, &mut external_state),
1787 native.sim(clock_reset, input, &mut native_state)
1788 );
1789 assert!(external_state == native_state);
1790 }
1791 }
1792
1793 fn live_job(job_id: u32, cluster: u8, frame_started: bool) -> BenchmarkLiveJob {
1794 BenchmarkLiveJob {
1795 live: true,
1796 job_id: b32(job_id as u128),
1797 cluster: b2(cluster as u128),
1798 frame_started,
1799 }
1800 }
1801
1802 fn active_engine_q(batch: u32) -> BenchmarkEngineWithSignerQ<InlineBenchmarkSigner<4, 2>> {
1803 BenchmarkEngineWithSignerQ::<InlineBenchmarkSigner<4, 2>> {
1804 signer: crate::SignerTopOutput::default(),
1805 control: BenchmarkEngineControl {
1806 busy: true,
1807 ..BenchmarkEngineControl::default()
1808 },
1809 data: BenchmarkEngineData {
1810 batch: b32(batch as u128),
1811 ..BenchmarkEngineData::default()
1812 },
1813 }
1814 }
1815
1816 fn engine_transition(
1817 mut q: BenchmarkEngineWithSignerQ<InlineBenchmarkSigner<4, 2>>,
1818 signer: crate::SignerTopOutput,
1819 ) -> (
1820 BenchmarkEngineOutput,
1821 BenchmarkEngineControl,
1822 BenchmarkEngineData,
1823 ) {
1824 q.signer = signer;
1825 let (output, d) = benchmark_engine_kernel::<InlineBenchmarkSigner<4, 2>>(
1826 clock_reset(clock(false), reset(false)),
1827 BenchmarkEngineInput::default(),
1828 q,
1829 );
1830 (output, d.control, d.data)
1831 }
1832
1833 fn axi_q(
1834 engine: BenchmarkEngineOutput,
1835 control: AxiLiteBenchmarkControl,
1836 config: AxiLiteBenchmarkConfig,
1837 ) -> AxiLiteBenchmarkKernelWithSignerQ<InlineBenchmarkSigner<4, 2>> {
1838 AxiLiteBenchmarkKernelWithSignerQ::<InlineBenchmarkSigner<4, 2>> {
1839 engine,
1840 control,
1841 config,
1842 }
1843 }
1844
1845 fn axi_transition(
1846 q: AxiLiteBenchmarkKernelWithSignerQ<InlineBenchmarkSigner<4, 2>>,
1847 input: AxiLiteBenchmarkInput,
1848 ) -> (
1849 AxiLiteBenchmarkOutput,
1850 AxiLiteBenchmarkControl,
1851 AxiLiteBenchmarkConfig,
1852 ) {
1853 let (output, d) = axi_lite_benchmark_kernel::<InlineBenchmarkSigner<4, 2>>(
1854 clock_reset(clock(false), reset(false)),
1855 input,
1856 q,
1857 );
1858 (output, d.control, d.config)
1859 }
1860
1861 #[test]
1862 fn launch_validation_reserves_a_representable_exclusive_end() {
1863 let ordinary = benchmark_launch_validation_kernel(BenchmarkLaunchValidationInput {
1864 batch: b32(17),
1865 base_index: b64(41),
1866 });
1867 assert!(ordinary.valid);
1868 assert_eq!(ordinary.next_base_index, b64(58));
1869 assert_eq!(ordinary.reject_code, b8(BENCHMARK_REJECT_NONE as u128));
1870
1871 let zero = benchmark_launch_validation_kernel(BenchmarkLaunchValidationInput {
1872 batch: b32(0),
1873 base_index: b64(41),
1874 });
1875 assert!(!zero.valid);
1876 assert_eq!(zero.reject_code, b8(BENCHMARK_REJECT_ZERO_BATCH as u128));
1877
1878 let overflow = benchmark_launch_validation_kernel(BenchmarkLaunchValidationInput {
1879 batch: b32(2),
1880 base_index: b64(u64::MAX as u128 - 1),
1881 });
1882 assert!(!overflow.valid);
1883 assert_eq!(
1884 overflow.reject_code,
1885 b8(BENCHMARK_REJECT_INDEX_OVERFLOW as u128)
1886 );
1887 }
1888
1889 #[test]
1890 fn seed_byte_order_is_exact_and_batch_material_is_injective() {
1891 let nonce_prefix = [
1892 b32(0x0302_0100),
1893 b32(0x0706_0504),
1894 b32(0x0b0a_0908),
1895 b32(0x0f0e_0d0c),
1896 b32(0x1312_1110),
1897 b32(0x1716_1514),
1898 ];
1899 let first = benchmark_job_material_kernel(nonce_prefix, b64(0x1829_3a4b_5c6d_7e8f));
1900 let seed = first.private_seed.map(raw8);
1901 assert_eq!(
1902 seed[..24],
1903 core::array::from_fn::<_, 24, _>(|index| index as u8)
1904 );
1905 assert_eq!(&seed[24..], &0x1829_3a4b_5c6d_7e8f_u64.to_be_bytes());
1906 assert_eq!(
1907 first.message.map(raw8),
1908 seed.map(|byte| byte ^ 0xa5),
1909 "message uses the documented test-only domain transform"
1910 );
1911
1912 let mut seeds = std::collections::BTreeSet::new();
1913 for offset in 0..257_u64 {
1914 let material = benchmark_job_material_kernel(nonce_prefix, b64(9_000 + offset as u128));
1915 assert!(seeds.insert(material.private_seed.map(raw8)));
1916 }
1917 }
1918
1919 fn canonical_output(job_id: u32, cluster: u8, beat: usize) -> SignerOutputBeat {
1920 let mut data = [b8(0); 64];
1921 for (lane, byte) in data.iter_mut().enumerate() {
1922 *byte = b8(((job_id as usize + beat * 17 + lane * 29) & 0xff) as u128);
1923 }
1924 let final_beat = beat == 34;
1925 if final_beat {
1926 for byte in &mut data[32..] {
1927 *byte = b8(0);
1928 }
1929 }
1930 SignerOutputBeat {
1931 beat: LaneLocalBeat {
1932 data,
1933 keep: if final_beat {
1934 b64(0xffff_ffff)
1935 } else {
1936 b64(u64::MAX as u128)
1937 },
1938 last: final_beat,
1939 job_id: b32(job_id as u128),
1940 },
1941 cluster: b2(cluster as u128),
1942 }
1943 }
1944
1945 #[test]
1946 fn independent_frame_checker_enforces_all_thirty_five_beats() {
1947 let mut active = false;
1948 let mut job_id = b32(0);
1949 let mut cluster = b2(0);
1950 for beat in 0..SIGNER_FRAME_BEATS {
1951 let output = canonical_output(7, 2, beat);
1952 assert!(benchmark_frame_is_canonical_kernel(
1953 BenchmarkFrameValidationInput {
1954 output,
1955 frame_active: active,
1956 expected_beat: b6(beat as u128),
1957 frame_job_id: job_id,
1958 frame_cluster: cluster,
1959 batch: b32(8),
1960 }
1961 ));
1962 if beat == 0 {
1963 active = true;
1964 job_id = output.beat.job_id;
1965 cluster = output.cluster;
1966 }
1967 }
1968
1969 let mut wrong_keep = canonical_output(7, 2, 34);
1970 wrong_keep.beat.keep = b64(u64::MAX as u128);
1971 assert!(!benchmark_frame_is_canonical_kernel(
1972 BenchmarkFrameValidationInput {
1973 output: wrong_keep,
1974 frame_active: true,
1975 expected_beat: b6(34),
1976 frame_job_id: b32(7),
1977 frame_cluster: b2(2),
1978 batch: b32(8),
1979 }
1980 ));
1981
1982 let wrong_job = canonical_output(8, 2, 1);
1983 assert!(!benchmark_frame_is_canonical_kernel(
1984 BenchmarkFrameValidationInput {
1985 output: wrong_job,
1986 frame_active: true,
1987 expected_beat: b6(1),
1988 frame_job_id: b32(7),
1989 frame_cluster: b2(2),
1990 batch: b32(8),
1991 }
1992 ));
1993 }
1994
1995 #[test]
1996 fn checksum_fold_is_wide_deterministic_and_identity_sensitive() {
1997 let initial = [b32(0); BENCHMARK_CHECKSUM_WORDS];
1998 let output = canonical_output(3, 1, 0);
1999 let first = benchmark_checksum_kernel(BenchmarkChecksumInput {
2000 checksum: initial,
2001 output,
2002 beat_index: b6(0),
2003 });
2004 assert!(first.iter().any(|word| *word != b32(0)));
2005 assert_eq!(
2006 first,
2007 benchmark_checksum_kernel(BenchmarkChecksumInput {
2008 checksum: initial,
2009 output,
2010 beat_index: b6(0),
2011 })
2012 );
2013 let different_identity = benchmark_checksum_kernel(BenchmarkChecksumInput {
2014 checksum: initial,
2015 output,
2016 beat_index: b6(1),
2017 });
2018 assert_ne!(first, different_identity);
2019 }
2020
2021 #[test]
2022 fn exact_twelve_entry_scoreboard_rejects_duplicates_and_cluster_overfill() {
2023 let mut live_jobs = [BenchmarkLiveJob::default(); BENCHMARK_LIVE_JOB_SLOTS];
2024 for job_id in 0..12_u32 {
2025 let cluster = (job_id % 3) as u8;
2026 let admission =
2027 benchmark_scoreboard_admission_kernel(BenchmarkScoreboardAdmissionInput {
2028 live_jobs,
2029 job_id: b32(job_id as u128),
2030 cluster: b2(cluster as u128),
2031 });
2032 assert!(
2033 admission.valid,
2034 "job {job_id} must fit its four-slot partition"
2035 );
2036 let slot = raw4(admission.slot);
2037 assert_eq!(slot / LANE_LOCAL_CONTEXTS, usize::from(cluster));
2038 live_jobs[slot] = live_job(job_id, cluster, false);
2039 }
2040 let audit = benchmark_scoreboard_audit_kernel(BenchmarkScoreboardAuditInput { live_jobs });
2041 assert!(audit.valid);
2042 assert_eq!(audit.live_count, b4(12));
2043 assert!(!audit.empty);
2044
2045 let cluster_zero_full =
2046 benchmark_scoreboard_admission_kernel(BenchmarkScoreboardAdmissionInput {
2047 live_jobs,
2048 job_id: b32(12),
2049 cluster: b2(0),
2050 });
2051 assert!(!cluster_zero_full.valid);
2052 let duplicate = benchmark_scoreboard_admission_kernel(BenchmarkScoreboardAdmissionInput {
2053 live_jobs,
2054 job_id: b32(7),
2055 cluster: b2(1),
2056 });
2057 assert!(!duplicate.valid);
2058
2059 let owner = benchmark_scoreboard_lookup_kernel(BenchmarkScoreboardLookupInput {
2060 live_jobs,
2061 job_id: b32(10),
2062 cluster: b2(1),
2063 });
2064 assert!(owner.valid);
2065 assert_eq!(owner.match_count, b4(1));
2066
2067 let mut corrupt = live_jobs;
2068 corrupt[0].job_id = corrupt[1].job_id;
2069 let corrupt_owner = benchmark_scoreboard_lookup_kernel(BenchmarkScoreboardLookupInput {
2070 live_jobs: corrupt,
2071 job_id: corrupt[1].job_id,
2072 cluster: corrupt[1].cluster,
2073 });
2074 assert!(
2075 !corrupt_owner.valid,
2076 "duplicate live identity must fail closed"
2077 );
2078 assert_eq!(corrupt_owner.match_count, b4(2));
2079 }
2080
2081 #[test]
2082 fn injected_admission_frame_error_and_counter_faults_fail_closed() {
2083 let active_clock = clock_reset(clock(false), reset(false));
2084
2085 let mut admission_q = active_engine_q(5);
2086 admission_q.control.accepted = b64(4);
2087 for slot in 0..4 {
2088 admission_q.control.live_jobs[slot] = live_job(slot as u32, 0, false);
2089 }
2090 admission_q.signer.start_ready = true;
2091 admission_q.signer.start_accepted = true;
2092 admission_q.signer.start_cluster = b2(0);
2093 admission_q.signer.cluster_start_ready[0] = true;
2094 let (admission, _) = benchmark_engine_kernel::<InlineBenchmarkSigner<4, 2>>(
2095 active_clock,
2096 BenchmarkEngineInput::default(),
2097 admission_q,
2098 );
2099 assert!(admission.fault);
2100 assert_eq!(admission.fault_code, b8(BENCHMARK_FAULT_ADMISSION as u128));
2101
2102 let mut frame_q = active_engine_q(1);
2103 frame_q.signer.output_valid = true;
2104 frame_q.signer.output = canonical_output(0, 0, 0);
2105 let (frame, _) = benchmark_engine_kernel::<InlineBenchmarkSigner<4, 2>>(
2106 active_clock,
2107 BenchmarkEngineInput::default(),
2108 frame_q,
2109 );
2110 assert!(frame.fault, "premature frame has no live owner");
2111 assert_eq!(frame.fault_code, b8(BENCHMARK_FAULT_FRAME as u128));
2112
2113 let mut error_q = active_engine_q(1);
2114 error_q.signer.error_valid = true;
2115 error_q.signer.delivered_error = true;
2116 error_q.signer.error.job_id = b32(0);
2117 error_q.signer.error.cluster = b2(0);
2118 let (error, _) = benchmark_engine_kernel::<InlineBenchmarkSigner<4, 2>>(
2119 active_clock,
2120 BenchmarkEngineInput::default(),
2121 error_q,
2122 );
2123 assert!(error.fault, "premature error has no live owner");
2124 assert_eq!(error.fault_code, b8(BENCHMARK_FAULT_ERROR as u128));
2125
2126 let mut counter_q = active_engine_q(1);
2127 counter_q.control.accepted = b64(1);
2128 counter_q.control.completed = b64(1);
2129 counter_q.control.beats = b64(35);
2130 counter_q.control.live_jobs[0] = live_job(0, 0, false);
2131 let (counter, _) = benchmark_engine_kernel::<InlineBenchmarkSigner<4, 2>>(
2132 active_clock,
2133 BenchmarkEngineInput::default(),
2134 counter_q,
2135 );
2136 assert!(
2137 counter.fault,
2138 "terminal count with a missing retirement must fail"
2139 );
2140 assert_eq!(counter.fault_code, b8(BENCHMARK_FAULT_COUNTER as u128));
2141 }
2142
2143 #[test]
2144 fn duplicate_and_success_error_double_terminals_cannot_retire_twice() {
2145 let active_clock = clock_reset(clock(false), reset(false));
2146 let mut q = active_engine_q(1);
2147 q.control.accepted = b64(1);
2148 q.control.beats = b64(34);
2149 q.control.frame_active = true;
2150 q.control.expected_beat = b6(34);
2151 q.control.frame_job_id = b32(0);
2152 q.control.frame_cluster = b2(0);
2153 q.control.live_jobs[0] = live_job(0, 0, true);
2154 q.signer.output_valid = true;
2155 q.signer.output = canonical_output(0, 0, 34);
2156 q.signer.delivered_final = true;
2157 q.signer.error_valid = true;
2158 q.signer.error = crate::SignerErrorCompletion {
2159 job_id: b32(0),
2160 cluster: b2(0),
2161 context: b2(0),
2162 };
2163 q.signer.delivered_error = true;
2164 let (double, _) = benchmark_engine_kernel::<InlineBenchmarkSigner<4, 2>>(
2165 active_clock,
2166 BenchmarkEngineInput::default(),
2167 q,
2168 );
2169 assert!(double.fault);
2170 assert_eq!(double.fault_code, b8(BENCHMARK_FAULT_ERROR as u128));
2171 assert_eq!(double.completed, b64(1));
2172 assert_eq!(double.errors, b64(0));
2173
2174 let mut duplicate_q = active_engine_q(2);
2175 duplicate_q.control.accepted = b64(1);
2176 duplicate_q.control.completed = b64(1);
2177 duplicate_q.control.beats = b64(35);
2178 duplicate_q.control.frame_active = true;
2179 duplicate_q.control.expected_beat = b6(34);
2180 duplicate_q.control.frame_job_id = b32(0);
2181 duplicate_q.control.frame_cluster = b2(0);
2182 duplicate_q.signer.output_valid = true;
2183 duplicate_q.signer.output = canonical_output(0, 0, 34);
2184 duplicate_q.signer.delivered_final = true;
2185 let (duplicate, _) = benchmark_engine_kernel::<InlineBenchmarkSigner<4, 2>>(
2186 active_clock,
2187 BenchmarkEngineInput::default(),
2188 duplicate_q,
2189 );
2190 assert!(duplicate.fault, "a retired owner cannot complete again");
2191 assert_eq!(duplicate.fault_code, b8(BENCHMARK_FAULT_FRAME as u128));
2192 }
2193
2194 fn software_checksum_fold(
2195 checksum: &mut [u32; BENCHMARK_CHECKSUM_WORDS],
2196 output: SignerOutputBeat,
2197 beat: usize,
2198 ) {
2199 let cluster: b8 = output.cluster.resize();
2200 let identity = raw32(output.beat.job_id) ^ beat as u32 ^ u32::from(raw8(cluster));
2201 for (word, checksum_word) in checksum.iter_mut().enumerate() {
2202 let bytes = [
2203 raw8(output.beat.data[word * 4]),
2204 raw8(output.beat.data[word * 4 + 1]),
2205 raw8(output.beat.data[word * 4 + 2]),
2206 raw8(output.beat.data[word * 4 + 3]),
2207 ];
2208 *checksum_word ^= u32::from_le_bytes(bytes) ^ identity;
2209 }
2210 }
2211
2212 #[test]
2213 fn thirteen_synthetic_jobs_reuse_capacity_and_finish_with_exact_beat_oracle() {
2214 let mut q = active_engine_q(13);
2215 let mut software_checksum = [0_u32; BENCHMARK_CHECKSUM_WORDS];
2216
2217 for job_id in 0..12_u32 {
2218 let cluster = (job_id % 3) as usize;
2219 let mut signer = crate::SignerTopOutput::default();
2220 signer.start_ready = true;
2221 signer.start_accepted = true;
2222 signer.start_cluster = b2(cluster as u128);
2223 signer.cluster_start_ready[cluster] = true;
2224 let (output, control, data) = engine_transition(q, signer);
2225 assert!(!output.fault, "admitting job {job_id}");
2226 q = BenchmarkEngineWithSignerQ::<InlineBenchmarkSigner<4, 2>> {
2227 signer: crate::SignerTopOutput::default(),
2228 control,
2229 data,
2230 };
2231 }
2232 assert_eq!(q.control.accepted, b64(12));
2233 assert_eq!(
2234 benchmark_scoreboard_audit_kernel(BenchmarkScoreboardAuditInput {
2235 live_jobs: q.control.live_jobs,
2236 })
2237 .live_count,
2238 b4(12)
2239 );
2240
2241 let mut terminal = BenchmarkEngineOutput::default();
2242 for job_id in 0..13_u32 {
2243 if job_id == 1 {
2244 let mut signer = crate::SignerTopOutput::default();
2245 signer.start_ready = true;
2246 signer.start_accepted = true;
2247 signer.start_cluster = b2(0);
2248 signer.cluster_start_ready[0] = true;
2249 let (output, control, data) = engine_transition(q, signer);
2250 assert!(
2251 !output.fault,
2252 "thirteenth job reuses the retired cluster-zero slot"
2253 );
2254 q = BenchmarkEngineWithSignerQ::<InlineBenchmarkSigner<4, 2>> {
2255 signer: crate::SignerTopOutput::default(),
2256 control,
2257 data,
2258 };
2259 }
2260 let actual_job = if job_id == 0 { 0 } else { job_id };
2261 let cluster = (actual_job % 3) as u8;
2262 for beat in 0..SIGNER_FRAME_BEATS {
2263 let frame = canonical_output(actual_job, cluster, beat);
2264 software_checksum_fold(&mut software_checksum, frame, beat);
2265 let mut signer = crate::SignerTopOutput::default();
2266 signer.output_valid = true;
2267 signer.output = frame;
2268 signer.delivered_final = beat + 1 == SIGNER_FRAME_BEATS;
2269 let (output, control, data) = engine_transition(q, signer);
2270 assert!(!output.fault, "job {actual_job}, beat {beat}");
2271 terminal = output;
2272 q = BenchmarkEngineWithSignerQ::<InlineBenchmarkSigner<4, 2>> {
2273 signer: crate::SignerTopOutput::default(),
2274 control,
2275 data,
2276 };
2277 }
2278 }
2279
2280 assert!(terminal.done);
2281 assert!(!terminal.busy);
2282 assert_eq!(terminal.accepted, b64(13));
2283 assert_eq!(terminal.completed, b64(13));
2284 assert_eq!(terminal.errors, b64(0));
2285 assert_eq!(terminal.beats, b64((13 * SIGNER_FRAME_BEATS) as u128));
2286 assert!(terminal.completion_span_cycles > b64(0));
2287 assert_eq!(terminal.checksum.map(raw32), software_checksum);
2288 assert!(
2289 benchmark_scoreboard_audit_kernel(BenchmarkScoreboardAuditInput {
2290 live_jobs: q.control.live_jobs,
2291 })
2292 .empty
2293 );
2294 }
2295
2296 #[test]
2297 fn nonce_index_payload_matches_an_independent_software_oracle() {
2298 let nonce_prefix = [
2299 b32(0x0123_4567),
2300 b32(0x89ab_cdef),
2301 b32(0x1020_3040),
2302 b32(0x5060_7080),
2303 b32(0x90a0_b0c0),
2304 b32(0xd0e0_f000),
2305 ];
2306 let software_nonce = nonce_prefix.map(raw32);
2307 for offset in 0..33_u64 {
2308 let index = 0x0102_0304_ffff_ff00_u64 + offset;
2309 let hardware = benchmark_job_material_kernel(nonce_prefix, b64(index as u128));
2310 let mut seed = [0_u8; 32];
2311 for (word, value) in software_nonce.iter().enumerate() {
2312 seed[word * 4..word * 4 + 4].copy_from_slice(&value.to_le_bytes());
2313 }
2314 seed[24..].copy_from_slice(&index.to_be_bytes());
2315 assert_eq!(hardware.private_seed.map(raw8), seed);
2316 assert_eq!(hardware.message.map(raw8), seed.map(|byte| byte ^ 0xa5));
2317 }
2318 }
2319
2320 #[test]
2321 fn axi_w_before_aw_and_simultaneous_aw_w_preserve_acceptance_state() {
2322 let busy_engine = BenchmarkEngineOutput {
2323 busy: true,
2324 ..BenchmarkEngineOutput::default()
2325 };
2326 let initial_config = AxiLiteBenchmarkConfig {
2327 batch: b32(7),
2328 ..AxiLiteBenchmarkConfig::default()
2329 };
2330 let (_output, w_control, w_config) = axi_transition(
2331 axi_q(
2332 busy_engine,
2333 AxiLiteBenchmarkControl::default(),
2334 initial_config,
2335 ),
2336 AxiLiteBenchmarkInput {
2337 w_valid: true,
2338 w_data: b32(99),
2339 w_strb: b4(0xf),
2340 ..AxiLiteBenchmarkInput::default()
2341 },
2342 );
2343 assert!(w_control.w_pending);
2344 assert!(w_control.w_locked_at_accept);
2345
2346 let idle_engine = BenchmarkEngineOutput {
2347 launch_ready: true,
2348 ..BenchmarkEngineOutput::default()
2349 };
2350 let (_output, paired_control, paired_config) = axi_transition(
2351 axi_q(idle_engine, w_control, w_config),
2352 AxiLiteBenchmarkInput {
2353 aw_valid: true,
2354 aw_addr: b12(REG_BATCH as u128),
2355 ..AxiLiteBenchmarkInput::default()
2356 },
2357 );
2358 assert!(paired_control.aw_pending);
2359 assert!(!paired_control.aw_locked_at_accept);
2360 let (_output, rejected_control, rejected_config) = axi_transition(
2361 axi_q(idle_engine, paired_control, paired_config),
2362 AxiLiteBenchmarkInput::default(),
2363 );
2364 assert!(rejected_control.b_valid);
2365 assert_eq!(rejected_control.b_resp, b2(AXI_RESP_SLVERR as u128));
2366 assert_eq!(rejected_config.batch, b32(7));
2367
2368 let (_output, simultaneous_control, simultaneous_config) = axi_transition(
2369 axi_q(
2370 idle_engine,
2371 AxiLiteBenchmarkControl::default(),
2372 initial_config,
2373 ),
2374 AxiLiteBenchmarkInput {
2375 aw_valid: true,
2376 aw_addr: b12(REG_BATCH as u128),
2377 w_valid: true,
2378 w_data: b32(123),
2379 w_strb: b4(0xf),
2380 ..AxiLiteBenchmarkInput::default()
2381 },
2382 );
2383 assert!(simultaneous_control.aw_pending);
2384 assert!(simultaneous_control.w_pending);
2385 let (_output, accepted_control, accepted_config) = axi_transition(
2386 axi_q(idle_engine, simultaneous_control, simultaneous_config),
2387 AxiLiteBenchmarkInput::default(),
2388 );
2389 assert_eq!(accepted_control.b_resp, b2(AXI_RESP_OKAY as u128));
2390 assert_eq!(accepted_config.batch, b32(123));
2391 }
2392
2393 #[test]
2394 fn axi_rejects_misaligned_read_only_and_busy_accepted_writes() {
2395 for address in [REG_BATCH + 1, REG_ABI_VERSION, REG_STATUS] {
2396 let control = AxiLiteBenchmarkControl {
2397 aw_pending: true,
2398 aw_addr: b12(address as u128),
2399 w_pending: true,
2400 w_data: b32(0xfeed_face),
2401 w_strb: b4(0xf),
2402 ..AxiLiteBenchmarkControl::default()
2403 };
2404 let config = AxiLiteBenchmarkConfig {
2405 batch: b32(17),
2406 ..AxiLiteBenchmarkConfig::default()
2407 };
2408 let (_output, next_control, next_config) = axi_transition(
2409 axi_q(BenchmarkEngineOutput::default(), control, config),
2410 AxiLiteBenchmarkInput::default(),
2411 );
2412 assert_eq!(next_control.b_resp, b2(AXI_RESP_SLVERR as u128));
2413 assert_eq!(next_config, config);
2414 }
2415
2416 for address in [REG_AP_CTRL, REG_BATCH, REG_NONCE_0, REG_BASE_LO] {
2417 let control = AxiLiteBenchmarkControl {
2418 aw_pending: true,
2419 aw_addr: b12(address as u128),
2420 aw_locked_at_accept: true,
2421 w_pending: true,
2422 w_data: b32(1),
2423 w_strb: b4(0xf),
2424 ..AxiLiteBenchmarkControl::default()
2425 };
2426 let (_output, next_control, _) = axi_transition(
2427 axi_q(
2428 BenchmarkEngineOutput::default(),
2429 control,
2430 AxiLiteBenchmarkConfig::default(),
2431 ),
2432 AxiLiteBenchmarkInput::default(),
2433 );
2434 assert_eq!(next_control.b_resp, b2(AXI_RESP_SLVERR as u128));
2435 assert!(!next_control.start_pending);
2436 }
2437 }
2438
2439 #[test]
2440 fn terminal_cycle_is_idle_but_an_earlier_busy_acceptance_stays_locked() {
2441 let terminal_engine = BenchmarkEngineOutput {
2442 done: true,
2443 launch_ready: false,
2444 ..BenchmarkEngineOutput::default()
2445 };
2446 let (_output, captured, config) = axi_transition(
2447 axi_q(
2448 terminal_engine,
2449 AxiLiteBenchmarkControl::default(),
2450 AxiLiteBenchmarkConfig::default(),
2451 ),
2452 AxiLiteBenchmarkInput {
2453 aw_valid: true,
2454 aw_addr: b12(REG_BATCH as u128),
2455 w_valid: true,
2456 w_data: b32(5),
2457 w_strb: b4(0xf),
2458 ..AxiLiteBenchmarkInput::default()
2459 },
2460 );
2461 assert!(!captured.aw_locked_at_accept);
2462 assert!(!captured.w_locked_at_accept);
2463 assert!(captured.done);
2464 let idle_engine = BenchmarkEngineOutput {
2465 launch_ready: true,
2466 ..BenchmarkEngineOutput::default()
2467 };
2468 let (_output, decoded, config) = axi_transition(
2469 axi_q(idle_engine, captured, config),
2470 AxiLiteBenchmarkInput::default(),
2471 );
2472 assert_eq!(decoded.b_resp, b2(AXI_RESP_OKAY as u128));
2473 assert_eq!(config.batch, b32(5));
2474
2475 let busy_snapshot = AxiLiteBenchmarkControl {
2476 aw_pending: true,
2477 aw_addr: b12(REG_BATCH as u128),
2478 aw_locked_at_accept: true,
2479 w_pending: true,
2480 w_data: b32(9),
2481 w_strb: b4(0xf),
2482 ..AxiLiteBenchmarkControl::default()
2483 };
2484 let (_output, rejected, unchanged) = axi_transition(
2485 axi_q(idle_engine, busy_snapshot, config),
2486 AxiLiteBenchmarkInput::default(),
2487 );
2488 assert_eq!(rejected.b_resp, b2(AXI_RESP_SLVERR as u128));
2489 assert_eq!(unchanged.batch, b32(5));
2490 }
2491
2492 #[test]
2493 fn overflow_rejection_preserves_base_and_interrupt_events_win_races() {
2494 let base = b64(u64::MAX as u128 - 1);
2495 let config = AxiLiteBenchmarkConfig {
2496 batch: b32(2),
2497 base_index: base,
2498 ..AxiLiteBenchmarkConfig::default()
2499 };
2500 let launch_rejected = BenchmarkEngineOutput {
2501 launch_rejected: true,
2502 reject_code: b8(BENCHMARK_REJECT_INDEX_OVERFLOW as u128),
2503 done: true,
2504 ..BenchmarkEngineOutput::default()
2505 };
2506 let (_output, rejected_control, rejected_config) = axi_transition(
2507 axi_q(
2508 launch_rejected,
2509 AxiLiteBenchmarkControl {
2510 start_pending: true,
2511 ..AxiLiteBenchmarkControl::default()
2512 },
2513 config,
2514 ),
2515 AxiLiteBenchmarkInput::default(),
2516 );
2517 assert_eq!(rejected_config.base_index, base);
2518 assert!(!rejected_control.start_pending);
2519 assert_eq!(
2520 rejected_control.last_reject_code,
2521 b8(BENCHMARK_REJECT_INDEX_OVERFLOW as u128)
2522 );
2523
2524 let race_control = AxiLiteBenchmarkControl {
2525 aw_pending: true,
2526 aw_addr: b12(REG_ISR as u128),
2527 w_pending: true,
2528 w_data: b32(3),
2529 w_strb: b4(1),
2530 gie: true,
2531 ier: b2(3),
2532 isr: b2(3),
2533 ..AxiLiteBenchmarkControl::default()
2534 };
2535 let events = BenchmarkEngineOutput {
2536 done: true,
2537 fault: true,
2538 ..BenchmarkEngineOutput::default()
2539 };
2540 let (race_output, raced, _) = axi_transition(
2541 axi_q(events, race_control, config),
2542 AxiLiteBenchmarkInput::default(),
2543 );
2544 assert!(race_output.interrupt);
2545 assert!(raced.done);
2546 assert!(raced.fault_seen);
2547 assert_eq!(raced.isr, b2(3), "new events win a same-cycle ISR toggle");
2548
2549 let clear_done = AxiLiteBenchmarkControl {
2550 done: true,
2551 ..AxiLiteBenchmarkControl::default()
2552 };
2553 let (_output, done_wins, _) = axi_transition(
2554 axi_q(events, clear_done, config),
2555 AxiLiteBenchmarkInput {
2556 ar_valid: true,
2557 ar_addr: b12(REG_AP_CTRL as u128),
2558 ..AxiLiteBenchmarkInput::default()
2559 },
2560 );
2561 assert!(done_wins.done, "new AP_DONE wins clear-on-read");
2562
2563 let seen_fault = AxiLiteBenchmarkControl {
2564 aw_pending: true,
2565 aw_addr: b12(REG_ISR as u128),
2566 w_pending: true,
2567 w_data: b32(2),
2568 w_strb: b4(1),
2569 isr: b2(2),
2570 fault_seen: true,
2571 ..AxiLiteBenchmarkControl::default()
2572 };
2573 let (_output, cleared, _) = axi_transition(
2574 axi_q(events, seen_fault, config),
2575 AxiLiteBenchmarkInput::default(),
2576 );
2577 assert_eq!(
2578 cleared.isr,
2579 b2(1),
2580 "fault does not reassert after fault_seen"
2581 );
2582 }
2583
2584 #[test]
2585 fn gie_and_ier_writes_gate_the_registered_isr_level_without_losing_events() {
2586 let gie_write = AxiLiteBenchmarkControl {
2587 aw_pending: true,
2588 aw_addr: b12(REG_GIE as u128),
2589 w_pending: true,
2590 w_data: b32(1),
2591 w_strb: b4(1),
2592 ier: b2(3),
2593 isr: b2(1),
2594 ..AxiLiteBenchmarkControl::default()
2595 };
2596 let (before_gie, gie_enabled, config) = axi_transition(
2597 axi_q(
2598 BenchmarkEngineOutput::default(),
2599 gie_write,
2600 AxiLiteBenchmarkConfig::default(),
2601 ),
2602 AxiLiteBenchmarkInput::default(),
2603 );
2604 assert!(
2605 !before_gie.interrupt,
2606 "GIE takes effect through registered state"
2607 );
2608 assert!(gie_enabled.gie);
2609
2610 let (after_gie, _, _) = axi_transition(
2611 axi_q(
2612 BenchmarkEngineOutput::default(),
2613 AxiLiteBenchmarkControl {
2614 b_valid: false,
2615 ..gie_enabled
2616 },
2617 config,
2618 ),
2619 AxiLiteBenchmarkInput::default(),
2620 );
2621 assert!(after_gie.interrupt);
2622
2623 let ier_disable = AxiLiteBenchmarkControl {
2624 aw_pending: true,
2625 aw_addr: b12(REG_IER as u128),
2626 w_pending: true,
2627 w_data: b32(0),
2628 w_strb: b4(1),
2629 gie: true,
2630 ier: b2(3),
2631 isr: b2(0),
2632 ..AxiLiteBenchmarkControl::default()
2633 };
2634 let done_event = BenchmarkEngineOutput {
2635 done: true,
2636 ..BenchmarkEngineOutput::default()
2637 };
2638 let (_before_disable, disabled, _) = axi_transition(
2639 axi_q(done_event, ier_disable, config),
2640 AxiLiteBenchmarkInput::default(),
2641 );
2642 assert_eq!(disabled.ier, b2(0));
2643 assert_eq!(disabled.isr, b2(1), "done remains sticky while masked");
2644 let (masked, _, _) = axi_transition(
2645 axi_q(
2646 BenchmarkEngineOutput::default(),
2647 AxiLiteBenchmarkControl {
2648 b_valid: false,
2649 ..disabled
2650 },
2651 config,
2652 ),
2653 AxiLiteBenchmarkInput::default(),
2654 );
2655 assert!(!masked.interrupt);
2656 }
2657
2658 #[test]
2659 fn byte_strobes_and_read_map_are_exact() {
2660 assert_eq!(
2661 benchmark_merge_write_kernel(b32(0x1122_3344), b32(0xaabb_ccdd), b4(0b0101)),
2662 b32(0x11bb_33dd)
2663 );
2664 let mut engine = BenchmarkEngineOutput::default();
2665 engine.completed = b64(0x0123_4567_89ab_cdef);
2666 engine.checksum[15] = b32(0xfeed_face);
2667 let common = BenchmarkReadDecodeInput {
2668 engine,
2669 ..BenchmarkReadDecodeInput::default()
2670 };
2671 let completed_low = benchmark_read_decode_kernel(BenchmarkReadDecodeInput {
2672 address: b12(0x050),
2673 ..common
2674 });
2675 let completed_high = benchmark_read_decode_kernel(BenchmarkReadDecodeInput {
2676 address: b12(0x054),
2677 ..common
2678 });
2679 let checksum_last = benchmark_read_decode_kernel(BenchmarkReadDecodeInput {
2680 address: b12(0x0cc),
2681 ..common
2682 });
2683 let misaligned = benchmark_read_decode_kernel(BenchmarkReadDecodeInput {
2684 address: b12(0x051),
2685 ..common
2686 });
2687 assert_eq!(raw32(completed_low.data), 0x89ab_cdef);
2688 assert_eq!(raw32(completed_high.data), 0x0123_4567);
2689 assert_eq!(raw32(checksum_last.data), 0xfeed_face);
2690 assert!(!misaligned.valid);
2691 }
2692}