1#![doc = "RHDL SHA-256 compression implementations and performance-lane building blocks."]
2#![forbid(unsafe_code)]
3
4use rhdl::prelude::*;
27use rhdl_fpga::core::dff::DFF;
28use rhdl_primitives::NoResetDff;
29
30pub mod farm;
32
33pub mod lane;
35
36pub const ROUND_COUNT: usize = 64;
38
39pub const BLOCK_BYTES: usize = 64;
41
42pub const DIGEST_BYTES: usize = 32;
44
45pub const ROUND_TOKEN_BITS: usize = (8 + 16) * 32;
47
48pub const LANE_DATAPATH_FFS: usize = ROUND_COUNT * ROUND_TOKEN_BITS;
50
51pub const THREE_LANE_DATAPATH_FFS: usize = 3 * LANE_DATAPATH_FFS;
53
54pub const SCHEDULER_TAG_BITS: usize = 32;
56
57pub const LANE_TAG_FFS: usize = ROUND_COUNT * SCHEDULER_TAG_BITS;
59
60pub const LANE_VALID_FFS: usize = ROUND_COUNT;
62
63pub const LANE_PIPELINE_FFS: usize = LANE_DATAPATH_FFS + LANE_TAG_FFS + LANE_VALID_FFS;
65
66pub const THREE_LANE_PIPELINE_FFS: usize = 3 * LANE_PIPELINE_FFS;
68
69pub const BLOCKS_FOR_32_BYTES: usize = padded_block_count(32);
71
72pub const BLOCKS_FOR_35_BYTES: usize = padded_block_count(35);
74
75pub const BLOCKS_FOR_64_BYTES: usize = padded_block_count(64);
77
78pub const BLOCKS_FOR_2144_BYTES: usize = padded_block_count(2_144);
80
81pub const INITIAL_STATE: [u32; 8] = [
83 0x6a09_e667,
84 0xbb67_ae85,
85 0x3c6e_f372,
86 0xa54f_f53a,
87 0x510e_527f,
88 0x9b05_688c,
89 0x1f83_d9ab,
90 0x5be0_cd19,
91];
92
93pub const ROUND_CONSTANTS: [u32; ROUND_COUNT] = [
95 0x428a_2f98,
96 0x7137_4491,
97 0xb5c0_fbcf,
98 0xe9b5_dba5,
99 0x3956_c25b,
100 0x59f1_11f1,
101 0x923f_82a4,
102 0xab1c_5ed5,
103 0xd807_aa98,
104 0x1283_5b01,
105 0x2431_85be,
106 0x550c_7dc3,
107 0x72be_5d74,
108 0x80de_b1fe,
109 0x9bdc_06a7,
110 0xc19b_f174,
111 0xe49b_69c1,
112 0xefbe_4786,
113 0x0fc1_9dc6,
114 0x240c_a1cc,
115 0x2de9_2c6f,
116 0x4a74_84aa,
117 0x5cb0_a9dc,
118 0x76f9_88da,
119 0x983e_5152,
120 0xa831_c66d,
121 0xb003_27c8,
122 0xbf59_7fc7,
123 0xc6e0_0bf3,
124 0xd5a7_9147,
125 0x06ca_6351,
126 0x1429_2967,
127 0x27b7_0a85,
128 0x2e1b_2138,
129 0x4d2c_6dfc,
130 0x5338_0d13,
131 0x650a_7354,
132 0x766a_0abb,
133 0x81c2_c92e,
134 0x9272_2c85,
135 0xa2bf_e8a1,
136 0xa81a_664b,
137 0xc24b_8b70,
138 0xc76c_51a3,
139 0xd192_e819,
140 0xd699_0624,
141 0xf40e_3585,
142 0x106a_a070,
143 0x19a4_c116,
144 0x1e37_6c08,
145 0x2748_774c,
146 0x34b0_bcb5,
147 0x391c_0cb3,
148 0x4ed8_aa4a,
149 0x5b9c_ca4f,
150 0x682e_6ff3,
151 0x748f_82ee,
152 0x78a5_636f,
153 0x84c8_7814,
154 0x8cc7_0208,
155 0x90be_fffa,
156 0xa450_6ceb,
157 0xbef9_a3f7,
158 0xc671_78f2,
159];
160
161pub const ROUND_CONSTANTS_RHDL: [b32; ROUND_COUNT] = [
163 b32(0x428a_2f98),
164 b32(0x7137_4491),
165 b32(0xb5c0_fbcf),
166 b32(0xe9b5_dba5),
167 b32(0x3956_c25b),
168 b32(0x59f1_11f1),
169 b32(0x923f_82a4),
170 b32(0xab1c_5ed5),
171 b32(0xd807_aa98),
172 b32(0x1283_5b01),
173 b32(0x2431_85be),
174 b32(0x550c_7dc3),
175 b32(0x72be_5d74),
176 b32(0x80de_b1fe),
177 b32(0x9bdc_06a7),
178 b32(0xc19b_f174),
179 b32(0xe49b_69c1),
180 b32(0xefbe_4786),
181 b32(0x0fc1_9dc6),
182 b32(0x240c_a1cc),
183 b32(0x2de9_2c6f),
184 b32(0x4a74_84aa),
185 b32(0x5cb0_a9dc),
186 b32(0x76f9_88da),
187 b32(0x983e_5152),
188 b32(0xa831_c66d),
189 b32(0xb003_27c8),
190 b32(0xbf59_7fc7),
191 b32(0xc6e0_0bf3),
192 b32(0xd5a7_9147),
193 b32(0x06ca_6351),
194 b32(0x1429_2967),
195 b32(0x27b7_0a85),
196 b32(0x2e1b_2138),
197 b32(0x4d2c_6dfc),
198 b32(0x5338_0d13),
199 b32(0x650a_7354),
200 b32(0x766a_0abb),
201 b32(0x81c2_c92e),
202 b32(0x9272_2c85),
203 b32(0xa2bf_e8a1),
204 b32(0xa81a_664b),
205 b32(0xc24b_8b70),
206 b32(0xc76c_51a3),
207 b32(0xd192_e819),
208 b32(0xd699_0624),
209 b32(0xf40e_3585),
210 b32(0x106a_a070),
211 b32(0x19a4_c116),
212 b32(0x1e37_6c08),
213 b32(0x2748_774c),
214 b32(0x34b0_bcb5),
215 b32(0x391c_0cb3),
216 b32(0x4ed8_aa4a),
217 b32(0x5b9c_ca4f),
218 b32(0x682e_6ff3),
219 b32(0x748f_82ee),
220 b32(0x78a5_636f),
221 b32(0x84c8_7814),
222 b32(0x8cc7_0208),
223 b32(0x90be_fffa),
224 b32(0xa450_6ceb),
225 b32(0xbef9_a3f7),
226 b32(0xc671_78f2),
227];
228
229const _: () = {
230 assert!(BLOCKS_FOR_32_BYTES == 1);
231 assert!(BLOCKS_FOR_35_BYTES == 1);
232 assert!(BLOCKS_FOR_64_BYTES == 2);
233 assert!(BLOCKS_FOR_2144_BYTES == 34);
234 assert!(ROUND_TOKEN_BITS == 768);
235 assert!(LANE_DATAPATH_FFS == 49_152);
236 assert!(THREE_LANE_DATAPATH_FFS == 147_456);
237 assert!(LANE_TAG_FFS == 2_048);
238 assert!(LANE_PIPELINE_FFS == 51_264);
239 assert!(THREE_LANE_PIPELINE_FFS == 153_792);
240};
241
242#[must_use]
244pub const fn rotate_right(word: u32, count: u32) -> u32 {
245 word.rotate_right(count)
246}
247
248#[must_use]
250pub const fn choose(x: u32, y: u32, z: u32) -> u32 {
251 (x & y) ^ (!x & z)
252}
253
254#[must_use]
256pub const fn majority(x: u32, y: u32, z: u32) -> u32 {
257 (x & y) ^ (x & z) ^ (y & z)
258}
259
260#[must_use]
262pub const fn big_sigma_zero(x: u32) -> u32 {
263 rotate_right(x, 2) ^ rotate_right(x, 13) ^ rotate_right(x, 22)
264}
265
266#[must_use]
268pub const fn big_sigma_one(x: u32) -> u32 {
269 rotate_right(x, 6) ^ rotate_right(x, 11) ^ rotate_right(x, 25)
270}
271
272#[must_use]
274pub const fn small_sigma_zero(x: u32) -> u32 {
275 rotate_right(x, 7) ^ rotate_right(x, 18) ^ (x >> 3)
276}
277
278#[must_use]
280pub const fn small_sigma_one(x: u32) -> u32 {
281 rotate_right(x, 17) ^ rotate_right(x, 19) ^ (x >> 10)
282}
283
284#[must_use]
286pub const fn expand_schedule(block: [u32; 16]) -> [u32; ROUND_COUNT] {
287 let mut schedule = [0_u32; ROUND_COUNT];
288 let mut index = 0;
289 while index < 16 {
290 schedule[index] = block[index];
291 index += 1;
292 }
293 while index < ROUND_COUNT {
294 schedule[index] = small_sigma_one(schedule[index - 2])
295 .wrapping_add(schedule[index - 7])
296 .wrapping_add(small_sigma_zero(schedule[index - 15]))
297 .wrapping_add(schedule[index - 16]);
298 index += 1;
299 }
300 schedule
301}
302
303#[allow(clippy::many_single_char_names)]
308#[must_use]
309pub const fn compress_block(chaining: [u32; 8], block: [u32; 16]) -> [u32; 8] {
310 let schedule = expand_schedule(block);
311 let mut a = chaining[0];
312 let mut b = chaining[1];
313 let mut c = chaining[2];
314 let mut d = chaining[3];
315 let mut e = chaining[4];
316 let mut f = chaining[5];
317 let mut g = chaining[6];
318 let mut h = chaining[7];
319 let mut round = 0;
320 while round < ROUND_COUNT {
321 let temporary_one = h
322 .wrapping_add(big_sigma_one(e))
323 .wrapping_add(choose(e, f, g))
324 .wrapping_add(ROUND_CONSTANTS[round])
325 .wrapping_add(schedule[round]);
326 let temporary_two = big_sigma_zero(a).wrapping_add(majority(a, b, c));
327 h = g;
328 g = f;
329 f = e;
330 e = d.wrapping_add(temporary_one);
331 d = c;
332 c = b;
333 b = a;
334 a = temporary_one.wrapping_add(temporary_two);
335 round += 1;
336 }
337 [
338 chaining[0].wrapping_add(a),
339 chaining[1].wrapping_add(b),
340 chaining[2].wrapping_add(c),
341 chaining[3].wrapping_add(d),
342 chaining[4].wrapping_add(e),
343 chaining[5].wrapping_add(f),
344 chaining[6].wrapping_add(g),
345 chaining[7].wrapping_add(h),
346 ]
347}
348
349#[must_use]
353pub const fn padded_block_count(message_len: usize) -> usize {
354 let complete = message_len / BLOCK_BYTES;
355 let remainder = message_len % BLOCK_BYTES;
356 complete + if remainder <= 55 { 1 } else { 2 }
357}
358
359#[must_use]
366pub fn padded_block(message: &[u8], block_index: usize) -> Option<[u8; BLOCK_BYTES]> {
367 let bit_len = u64::try_from(message.len()).ok()?.checked_mul(8)?;
368 let block_count = padded_block_count(message.len());
369 if block_index >= block_count {
370 return None;
371 }
372 let final_len_start = block_count * BLOCK_BYTES - 8;
373 let start = block_index * BLOCK_BYTES;
374 let length_bytes = bit_len.to_be_bytes();
375 let mut block = [0_u8; BLOCK_BYTES];
376 let mut offset = 0;
377 while offset < BLOCK_BYTES {
378 let absolute = start + offset;
379 block[offset] = if absolute < message.len() {
380 message[absolute]
381 } else if absolute == message.len() {
382 0x80
383 } else if absolute >= final_len_start {
384 length_bytes[absolute - final_len_start]
385 } else {
386 0
387 };
388 offset += 1;
389 }
390 Some(block)
391}
392
393#[must_use]
395pub const fn decode_block(block: [u8; BLOCK_BYTES]) -> [u32; 16] {
396 let mut words = [0_u32; 16];
397 let mut index = 0;
398 while index < 16 {
399 let offset = index * 4;
400 words[index] = u32::from_be_bytes([
401 block[offset],
402 block[offset + 1],
403 block[offset + 2],
404 block[offset + 3],
405 ]);
406 index += 1;
407 }
408 words
409}
410
411#[must_use]
419pub fn sha256(message: &[u8]) -> [u8; DIGEST_BYTES] {
420 let mut chaining = INITIAL_STATE;
421 let blocks = padded_block_count(message.len());
422 let mut block_index = 0;
423 while block_index < blocks {
424 let block = padded_block(message, block_index)
425 .expect("padded SHA-256 block must exist for an in-range index");
426 chaining = compress_block(chaining, decode_block(block));
427 block_index += 1;
428 }
429 let mut digest = [0_u8; DIGEST_BYTES];
430 let mut word = 0;
431 while word < 8 {
432 let bytes = chaining[word].to_be_bytes();
433 digest[word * 4] = bytes[0];
434 digest[word * 4 + 1] = bytes[1];
435 digest[word * 4 + 2] = bytes[2];
436 digest[word * 4 + 3] = bytes[3];
437 word += 1;
438 }
439 digest
440}
441
442#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
444pub struct WorkingState {
445 pub a: b32,
447 pub b: b32,
449 pub c: b32,
451 pub d: b32,
453 pub e: b32,
455 pub f: b32,
457 pub g: b32,
459 pub h: b32,
461}
462
463impl WorkingState {
464 #[must_use]
466 pub fn from_words(words: [u32; 8]) -> Self {
467 Self {
468 a: b32(u128::from(words[0])),
469 b: b32(u128::from(words[1])),
470 c: b32(u128::from(words[2])),
471 d: b32(u128::from(words[3])),
472 e: b32(u128::from(words[4])),
473 f: b32(u128::from(words[5])),
474 g: b32(u128::from(words[6])),
475 h: b32(u128::from(words[7])),
476 }
477 }
478
479 #[must_use]
481 pub fn to_words(self) -> [u32; 8] {
482 [
483 bits32_to_u32(self.a),
484 bits32_to_u32(self.b),
485 bits32_to_u32(self.c),
486 bits32_to_u32(self.d),
487 bits32_to_u32(self.e),
488 bits32_to_u32(self.f),
489 bits32_to_u32(self.g),
490 bits32_to_u32(self.h),
491 ]
492 }
493}
494
495fn bits32_to_u32(word: b32) -> u32 {
496 let bytes = word.raw().to_le_bytes();
497 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
498}
499
500#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
506pub struct RoundState {
507 pub working: WorkingState,
509 pub schedule: [b32; 16],
511}
512
513impl RoundState {
514 #[must_use]
516 pub fn new(chaining: [u32; 8], block: [u32; 16]) -> Self {
517 Self {
518 working: WorkingState::from_words(chaining),
519 schedule: block.map(|word| b32(u128::from(word))),
520 }
521 }
522}
523
524#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
526pub struct PipelineWord {
527 pub state: RoundState,
529 pub tag: b32,
531 pub valid: bool,
533}
534
535#[kernel]
537pub fn big_sigma_zero_kernel(x: b32) -> b32 {
538 ((x >> 2) | (x << 30)) ^ ((x >> 13) | (x << 19)) ^ ((x >> 22) | (x << 10))
539}
540
541#[kernel]
543pub fn big_sigma_one_kernel(x: b32) -> b32 {
544 ((x >> 6) | (x << 26)) ^ ((x >> 11) | (x << 21)) ^ ((x >> 25) | (x << 7))
545}
546
547#[kernel]
549pub fn small_sigma_zero_kernel(x: b32) -> b32 {
550 ((x >> 7) | (x << 25)) ^ ((x >> 18) | (x << 14)) ^ (x >> 3)
551}
552
553#[kernel]
555pub fn small_sigma_one_kernel(x: b32) -> b32 {
556 ((x >> 17) | (x << 15)) ^ ((x >> 19) | (x << 13)) ^ (x >> 10)
557}
558
559#[kernel]
561pub fn choose_kernel(x: b32, y: b32, z: b32) -> b32 {
562 (x & y) ^ (!x & z)
563}
564
565#[kernel]
567pub fn majority_kernel(x: b32, y: b32, z: b32) -> b32 {
568 (x & y) ^ (x & z) ^ (y & z)
569}
570
571#[kernel]
573pub fn schedule_kernel(schedule: [b32; 16]) -> [b32; 16] {
574 let appended = small_sigma_one_kernel(schedule[14])
575 + schedule[9]
576 + small_sigma_zero_kernel(schedule[1])
577 + schedule[0];
578 let mut next = schedule;
579 for index in 0..15 {
580 next[index] = schedule[index + 1];
581 }
582 next[15] = appended;
583 next
584}
585
586#[kernel]
592pub fn round_kernel(state: RoundState, round_constant: b32) -> RoundState {
593 let temporary_one = state.working.h
594 + big_sigma_one_kernel(state.working.e)
595 + choose_kernel(state.working.e, state.working.f, state.working.g)
596 + round_constant
597 + state.schedule[0];
598 let temporary_two = big_sigma_zero_kernel(state.working.a)
599 + majority_kernel(state.working.a, state.working.b, state.working.c);
600 RoundState {
601 working: WorkingState {
602 a: temporary_one + temporary_two,
603 b: state.working.a,
604 c: state.working.b,
605 d: state.working.c,
606 e: state.working.d + temporary_one,
607 f: state.working.e,
608 g: state.working.f,
609 h: state.working.g,
610 },
611 schedule: schedule_kernel(state.schedule),
612 }
613}
614
615#[kernel]
617pub fn feed_forward_kernel(chaining: [b32; 8], working: WorkingState) -> [b32; 8] {
618 [
619 chaining[0] + working.a,
620 chaining[1] + working.b,
621 chaining[2] + working.c,
622 chaining[3] + working.d,
623 chaining[4] + working.e,
624 chaining[5] + working.f,
625 chaining[6] + working.g,
626 chaining[7] + working.h,
627 ]
628}
629
630#[allow(clippy::needless_range_loop)]
635#[kernel]
636pub fn compression_kernel(chaining: [b32; 8], block: [b32; 16]) -> [b32; 8] {
637 let mut state = RoundState {
638 working: WorkingState {
639 a: chaining[0],
640 b: chaining[1],
641 c: chaining[2],
642 d: chaining[3],
643 e: chaining[4],
644 f: chaining[5],
645 g: chaining[6],
646 h: chaining[7],
647 },
648 schedule: block,
649 };
650 for round in 0..64 {
651 state = round_kernel(state, ROUND_CONSTANTS_RHDL[round]);
652 }
653 feed_forward_kernel(chaining, state.working)
654}
655
656#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
668#[rhdl(dq_no_prefix)]
669pub struct RoundPipeline<const ROUNDS: usize>
670where
671 rhdl::bits::W<ROUNDS>: BitWidth,
672{
673 data: [NoResetDff<RoundState>; ROUNDS],
674 tag: [NoResetDff<b32>; ROUNDS],
675 valid: [DFF<bool>; ROUNDS],
676}
677
678impl<const ROUNDS: usize> Default for RoundPipeline<ROUNDS>
679where
680 rhdl::bits::W<ROUNDS>: BitWidth,
681{
682 fn default() -> Self {
683 assert!(
684 ROUNDS > 0,
685 "a SHA-256 round pipeline needs at least one stage"
686 );
687 assert!(
688 ROUNDS <= ROUND_COUNT,
689 "a SHA-256 round pipeline cannot exceed 64 stages"
690 );
691 Self {
692 data: core::array::from_fn(|_| NoResetDff::new()),
693 tag: core::array::from_fn(|_| NoResetDff::new()),
694 valid: core::array::from_fn(|_| DFF::new(false)),
695 }
696 }
697}
698
699impl<const ROUNDS: usize> SynchronousIO for RoundPipeline<ROUNDS>
700where
701 rhdl::bits::W<ROUNDS>: BitWidth,
702{
703 type I = PipelineWord;
704 type O = PipelineWord;
705 type Kernel = round_pipeline_kernel<ROUNDS>;
706}
707
708#[allow(clippy::needless_range_loop)]
710#[kernel]
711pub fn round_pipeline_kernel<const ROUNDS: usize>(
712 clock_reset: ClockReset,
713 input: PipelineWord,
714 q: Q<ROUNDS>,
715) -> (PipelineWord, D<ROUNDS>)
716where
717 rhdl::bits::W<ROUNDS>: BitWidth,
718{
719 let _ = clock_reset;
720 let mut d = D::<ROUNDS> {
724 data: q.data,
725 tag: q.tag,
726 valid: q.valid,
727 };
728 d.data[0] = round_kernel(input.state, ROUND_CONSTANTS_RHDL[0]);
729 d.tag[0] = input.tag;
730 d.valid[0] = input.valid;
731 for stage in 1..ROUNDS {
732 d.data[stage] = round_kernel(q.data[stage - 1], ROUND_CONSTANTS_RHDL[stage]);
733 d.tag[stage] = q.tag[stage - 1];
734 d.valid[stage] = q.valid[stage - 1];
735 }
736 (
737 PipelineWord {
738 state: q.data[ROUNDS - 1],
739 tag: q.tag[ROUNDS - 1],
740 valid: q.valid[ROUNDS - 1],
741 },
742 d,
743 )
744}
745
746pub fn round_verilog() -> Result<String, RHDLError> {
752 let rtl = compile_design::<round_kernel>(CompilationMode::Synchronous)?;
753 Ok(rtl.as_vlog()?.pretty())
754}
755
756pub fn compression_verilog() -> Result<String, RHDLError> {
762 let rtl = compile_design::<compression_kernel>(CompilationMode::Synchronous)?;
763 Ok(rtl.as_vlog()?.pretty())
764}
765
766pub fn round_pipeline_verilog<const ROUNDS: usize>() -> Result<String, RHDLError>
773where
774 rhdl::bits::W<ROUNDS>: BitWidth,
775{
776 let pipeline = RoundPipeline::<ROUNDS>::default();
777 Ok(pipeline
778 .descriptor("sha256_round_pipeline".into())?
779 .hdl()?
780 .modules
781 .pretty())
782}
783
784pub fn round_pipeline_unchecked_verilog<const ROUNDS: usize>() -> Result<String, RHDLError>
800where
801 rhdl::bits::W<ROUNDS>: BitWidth,
802{
803 let pipeline = RoundPipeline::<ROUNDS>::default();
804 let descriptor = pipeline.descriptor("sha256_round_pipeline".into())?;
805 let hdl = descriptor.hdl_unchecked()?;
806 Ok(hdl.modules.pretty())
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812 use rhdl::core::sim::ResetOrData;
813 use rhdl::core::sim::testbench::kernel::test_kernel_vm_and_verilog_synchronous;
814 use sha2::{Digest, Sha256};
815
816 const ABC_BLOCK: [u32; 16] = [
817 0x6162_6380,
818 0,
819 0,
820 0,
821 0,
822 0,
823 0,
824 0,
825 0,
826 0,
827 0,
828 0,
829 0,
830 0,
831 0,
832 0x0000_0018,
833 ];
834
835 fn words_to_bits<const N: usize>(words: [u32; N]) -> [b32; N] {
836 words.map(|word| b32(u128::from(word)))
837 }
838
839 fn bits_to_words<const N: usize>(words: [b32; N]) -> [u32; N] {
840 words.map(bits32_to_u32)
841 }
842
843 fn deterministic_bytes(length: usize) -> Vec<u8> {
844 let mut state = 0x6d2b_79f5_u32;
845 (0..length)
846 .map(|_| {
847 state ^= state << 13;
848 state ^= state >> 17;
849 state ^= state << 5;
850 state.to_le_bytes()[0]
851 })
852 .collect()
853 }
854
855 fn pipeline_token(marker: u32, tag: u128) -> PipelineWord {
856 let mut block = ABC_BLOCK;
857 block[0] ^= marker;
858 block[7] = marker.rotate_left(11);
859 PipelineWord {
860 state: RoundState::new(INITIAL_STATE, block),
861 tag: b32(tag),
862 valid: true,
863 }
864 }
865
866 fn after_two_rounds(token: &PipelineWord) -> PipelineWord {
867 PipelineWord {
868 state: round_kernel(
869 round_kernel(token.state, ROUND_CONSTANTS_RHDL[0]),
870 ROUND_CONSTANTS_RHDL[1],
871 ),
872 ..*token
873 }
874 }
875
876 fn two_round_pipeline_stream()
877 -> impl Iterator<Item = TimedSample<(ClockReset, PipelineWord)>> + Clone {
878 let bubble = PipelineWord::default();
879 [
880 ResetOrData::Reset,
881 ResetOrData::Data(pipeline_token(0x1111_1111, 1)),
882 ResetOrData::Data(bubble),
883 ResetOrData::Data(pipeline_token(0x2222_2222, 2)),
884 ResetOrData::Reset,
887 ResetOrData::Data(pipeline_token(0x9999_9999, 9)),
888 ResetOrData::Data(bubble),
889 ResetOrData::Data(pipeline_token(0xffff_ffff, 15)),
890 ResetOrData::Data(bubble),
891 ResetOrData::Data(bubble),
892 ]
893 .into_iter()
894 .clock_pos_edge(10)
895 }
896
897 #[test]
898 fn constants_and_fixed_block_counts_are_exact() {
899 assert_eq!(ROUND_CONSTANTS.len(), 64);
900 assert_eq!(ROUND_CONSTANTS_RHDL.len(), 64);
901 assert_eq!(BLOCKS_FOR_32_BYTES, 1);
902 assert_eq!(BLOCKS_FOR_35_BYTES, 1);
903 assert_eq!(BLOCKS_FOR_64_BYTES, 2);
904 assert_eq!(BLOCKS_FOR_2144_BYTES, 34);
905 for (software, hardware) in ROUND_CONSTANTS.into_iter().zip(ROUND_CONSTANTS_RHDL) {
906 assert_eq!(software, bits32_to_u32(hardware));
907 }
908 }
909
910 #[test]
911 fn standard_known_answer_tests() {
912 assert_eq!(
913 hex::encode(sha256(b"")),
914 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
915 );
916 assert_eq!(
917 hex::encode(sha256(b"abc")),
918 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
919 );
920 assert_eq!(
921 compress_block(INITIAL_STATE, ABC_BLOCK),
922 [
923 0xba78_16bf,
924 0x8f01_cfea,
925 0x4141_40de,
926 0x5dae_2223,
927 0xb003_61a3,
928 0x9617_7a9c,
929 0xb410_ff61,
930 0xf200_15ad,
931 ]
932 );
933 }
934
935 #[test]
936 fn fixed_sizes_and_boundaries_match_sha2() {
937 for length in [0, 1, 3, 32, 35, 55, 56, 63, 64, 65, 127, 128, 2_144] {
938 let message = deterministic_bytes(length);
939 let expected: [u8; 32] = Sha256::digest(&message).into();
940 assert_eq!(sha256(&message), expected, "length {length}");
941 }
942 }
943
944 #[test]
945 fn padding_stream_places_delimiter_and_length_exactly() {
946 let message = [0xa5_u8; 64];
947 let first = padded_block(&message, 0).expect("first block");
948 let second = padded_block(&message, 1).expect("padding block");
949 assert_eq!(first, message);
950 assert_eq!(second[0], 0x80);
951 assert!(second[1..56].iter().all(|byte| *byte == 0));
952 assert_eq!(&second[56..], &512_u64.to_be_bytes());
953 assert!(padded_block(&message, 2).is_none());
954 }
955
956 #[test]
957 fn rolling_rounds_match_expanded_schedule_and_software_compression() {
958 let schedule = expand_schedule(ABC_BLOCK);
959 let mut token = RoundState::new(INITIAL_STATE, ABC_BLOCK);
960 let chaining = words_to_bits(INITIAL_STATE);
961 for (schedule_word, round_constant) in schedule.into_iter().zip(ROUND_CONSTANTS_RHDL) {
962 assert_eq!(bits32_to_u32(token.schedule[0]), schedule_word);
963 token = round_kernel(token, round_constant);
964 }
965 let digest = feed_forward_kernel(chaining, token.working);
966 assert_eq!(
967 bits_to_words(digest),
968 compress_block(INITIAL_STATE, ABC_BLOCK)
969 );
970 }
971
972 #[test]
973 fn round_kernel_passes_rhdl_vm_and_emitted_verilog() -> Result<(), RHDLError> {
974 let mut state = RoundState::new(INITIAL_STATE, ABC_BLOCK);
975 let mut cases = Vec::new();
976 for constant in ROUND_CONSTANTS_RHDL.into_iter().take(4) {
977 cases.push((state, constant));
978 state = round_kernel(state, constant);
979 }
980 test_kernel_vm_and_verilog_synchronous::<round_kernel, _, _, _>(
981 round_kernel,
982 cases.into_iter(),
983 )
984 }
985
986 #[test]
987 fn compression_kernel_passes_rhdl_vm_and_emitted_verilog() -> Result<(), RHDLError> {
988 let chaining = words_to_bits(INITIAL_STATE);
989 let block = words_to_bits(ABC_BLOCK);
990 let expected = words_to_bits(compress_block(INITIAL_STATE, ABC_BLOCK));
991 assert_eq!(compression_kernel(chaining, block), expected);
992 test_kernel_vm_and_verilog_synchronous::<compression_kernel, _, _, _>(
993 compression_kernel,
994 [(chaining, block)].into_iter(),
995 )
996 }
997
998 #[test]
999 fn round_pipeline_preserves_valid_state_and_tag_across_reset() -> Result<(), RHDLError> {
1000 let pipeline = RoundPipeline::<2>::default();
1001 let samples = pipeline
1002 .run(two_round_pipeline_stream())
1003 .synchronous_sample()
1004 .collect::<Vec<_>>();
1005
1006 let observed = samples
1007 .iter()
1008 .enumerate()
1009 .filter_map(|(cycle, sample)| sample.output.valid.then_some((cycle, sample.output)))
1010 .collect::<Vec<_>>();
1011 let expected = [
1012 (3, after_two_rounds(&pipeline_token(0x1111_1111, 1))),
1013 (7, after_two_rounds(&pipeline_token(0x9999_9999, 9))),
1014 (9, after_two_rounds(&pipeline_token(0xffff_ffff, 15))),
1015 ];
1016 assert_eq!(observed, expected);
1017 assert!(
1018 !observed.iter().any(|(_, output)| output.tag == b32(2)),
1019 "reset must suppress every valid token already in flight"
1020 );
1021
1022 let test_bench = pipeline
1026 .run(two_round_pipeline_stream())
1027 .collect::<SynchronousTestBench<_, _>>();
1028 test_bench
1029 .rtl(&pipeline, &TestBenchOptions::default().skip(6))?
1030 .run_iverilog()?;
1031 Ok(())
1032 }
1033
1034 #[test]
1035 fn complete_round_pipeline_has_exact_latency_and_opaque_tag() {
1036 let input = pipeline_token(0xa5a5_5a5a, 9);
1037 let mut expected = input;
1038 for round_constant in ROUND_CONSTANTS_RHDL {
1039 expected.state = round_kernel(expected.state, round_constant);
1040 }
1041
1042 let mut stimulus = vec![ResetOrData::Reset, ResetOrData::Data(input)];
1043 stimulus.extend(std::iter::repeat_n(
1044 ResetOrData::Data(PipelineWord::default()),
1045 ROUND_COUNT,
1046 ));
1047 let pipeline = RoundPipeline::<ROUND_COUNT>::default();
1048 let observed = pipeline
1049 .run(stimulus.into_iter().clock_pos_edge(10))
1050 .synchronous_sample()
1051 .enumerate()
1052 .filter_map(|(cycle, sample)| sample.output.valid.then_some((cycle, sample.output)))
1053 .collect::<Vec<_>>();
1054 assert_eq!(observed, [(ROUND_COUNT + 1, expected)]);
1055 }
1056
1057 #[test]
1058 #[ignore = "full 64-stage RHDL lowering takes about four minutes"]
1059 fn complete_round_pipeline_specialization_renders_unchecked() -> Result<(), RHDLError> {
1060 let hdl = round_pipeline_unchecked_verilog::<ROUND_COUNT>()?;
1061 assert!(hdl.contains("module sha256_round_pipeline"));
1062 assert_eq!(hdl.matches("if (reset)").count(), ROUND_COUNT);
1063 assert_eq!(
1064 hdl.matches("always @(posedge clock)").count(),
1065 3 * ROUND_COUNT
1066 );
1067 Ok(())
1068 }
1069
1070 #[test]
1071 fn generators_expose_round_compression_and_resetless_pipeline() -> Result<(), RHDLError> {
1072 let round = round_verilog()?;
1073 assert!(round.contains("function [767:0] kernel_round_kernel"));
1074 let compression = compression_verilog()?;
1075 assert!(compression.contains("function [255:0] kernel_compression_kernel"));
1076 let pipeline = round_pipeline_verilog::<2>()?;
1077 assert!(pipeline.contains("module sha256_round_pipeline"));
1078 assert!(pipeline.contains("always @(posedge clock)"));
1079 assert!(pipeline.contains("if (reset)"));
1080 assert_eq!(pipeline.matches("if (reset)").count(), 2);
1081 Ok(())
1082 }
1083}