1use rhdl::core::ReplicatedSynchronous;
20use rhdl::prelude::*;
21
22use crate::{
23 LANE_PIPELINE_FFS, ROUND_COUNT,
24 lane::{
25 CompressionInput, CompressionLane, CompressionOutput, FULL_LANE_ADDRESS_BITS,
26 FULL_LANE_CHAINING_STORAGE_BITS,
27 },
28};
29
30pub const PERFORMANCE_FARM_LANES: usize = 3;
32
33pub const THREE_LANE_PROJECTED_PIPELINE_REGISTER_BITS: usize =
38 PERFORMANCE_FARM_LANES * LANE_PIPELINE_FFS;
39
40pub const THREE_LANE_PROJECTED_POINTER_REGISTER_BITS: usize =
42 PERFORMANCE_FARM_LANES * FULL_LANE_ADDRESS_BITS;
43
44pub const THREE_LANE_PROJECTED_BRAM_OUTPUT_REGISTER_BITS: usize = PERFORMANCE_FARM_LANES * 8 * 32;
49
50pub const THREE_LANE_PROJECTED_ARCHITECTURAL_REGISTER_BITS: usize =
55 THREE_LANE_PROJECTED_PIPELINE_REGISTER_BITS
56 + THREE_LANE_PROJECTED_POINTER_REGISTER_BITS
57 + THREE_LANE_PROJECTED_BRAM_OUTPUT_REGISTER_BITS;
58
59pub const THREE_LANE_PROJECTED_CHAINING_MEMORY_BITS: usize =
64 PERFORMANCE_FARM_LANES * FULL_LANE_CHAINING_STORAGE_BITS;
65
66const _: () = {
67 assert!(THREE_LANE_PROJECTED_PIPELINE_REGISTER_BITS == 153_792);
68 assert!(THREE_LANE_PROJECTED_POINTER_REGISTER_BITS == 18);
69 assert!(THREE_LANE_PROJECTED_BRAM_OUTPUT_REGISTER_BITS == 768);
70 assert!(THREE_LANE_PROJECTED_ARCHITECTURAL_REGISTER_BITS == 154_578);
71 assert!(THREE_LANE_PROJECTED_CHAINING_MEMORY_BITS == 49_152);
72};
73
74pub type CompressionFarmInput<const LANES: usize> = [CompressionInput; LANES];
76
77pub type CompressionFarmOutput<const LANES: usize> = [CompressionOutput; LANES];
79
80#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
91#[rhdl(dq_no_prefix)]
92pub struct CompressionFarm<const LANES: usize, const ROUNDS: usize, const ADDRESS_BITS: usize>
93where
94 rhdl::bits::W<ROUNDS>: BitWidth,
95 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
96{
97 lanes: [CompressionLane<ROUNDS, ADDRESS_BITS>; LANES],
98}
99
100impl<const LANES: usize, const ROUNDS: usize, const ADDRESS_BITS: usize> Default
101 for CompressionFarm<LANES, ROUNDS, ADDRESS_BITS>
102where
103 rhdl::bits::W<ROUNDS>: BitWidth,
104 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
105{
106 fn default() -> Self {
107 assert!(LANES > 0, "a compression farm needs at least one lane");
108 Self {
109 lanes: core::array::from_fn(|_| CompressionLane::default()),
110 }
111 }
112}
113
114impl<const LANES: usize, const ROUNDS: usize, const ADDRESS_BITS: usize> SynchronousIO
115 for CompressionFarm<LANES, ROUNDS, ADDRESS_BITS>
116where
117 rhdl::bits::W<ROUNDS>: BitWidth,
118 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
119{
120 type I = CompressionFarmInput<LANES>;
121 type O = CompressionFarmOutput<LANES>;
122 type Kernel = compression_farm_kernel<LANES, ROUNDS, ADDRESS_BITS>;
123}
124
125#[kernel]
127pub fn compression_farm_kernel<const LANES: usize, const ROUNDS: usize, const ADDRESS_BITS: usize>(
128 clock_reset: ClockReset,
129 input: CompressionFarmInput<LANES>,
130 q: Q<LANES, ROUNDS, ADDRESS_BITS>,
131) -> (CompressionFarmOutput<LANES>, D<LANES, ROUNDS, ADDRESS_BITS>)
132where
133 rhdl::bits::W<ROUNDS>: BitWidth,
134 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
135{
136 let _ = clock_reset;
137 let d = D::<LANES, ROUNDS, ADDRESS_BITS> { lanes: input };
138 (q.lanes, d)
139}
140
141#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
153pub struct ReplicatedCompressionFarm<
154 const LANES: usize,
155 const ROUNDS: usize,
156 const ADDRESS_BITS: usize,
157> where
158 rhdl::bits::W<ROUNDS>: BitWidth,
159 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
160{
161 lanes: ReplicatedSynchronous<CompressionLane<ROUNDS, ADDRESS_BITS>, LANES>,
162}
163
164impl<const LANES: usize, const ROUNDS: usize, const ADDRESS_BITS: usize> Default
165 for ReplicatedCompressionFarm<LANES, ROUNDS, ADDRESS_BITS>
166where
167 rhdl::bits::W<ROUNDS>: BitWidth,
168 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
169{
170 fn default() -> Self {
171 Self {
172 lanes: ReplicatedSynchronous::new(CompressionLane::default()),
173 }
174 }
175}
176
177impl<const LANES: usize, const ROUNDS: usize, const ADDRESS_BITS: usize> SynchronousIO
178 for ReplicatedCompressionFarm<LANES, ROUNDS, ADDRESS_BITS>
179where
180 rhdl::bits::W<ROUNDS>: BitWidth,
181 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
182{
183 type I = CompressionFarmInput<LANES>;
184 type O = CompressionFarmOutput<LANES>;
185 type Kernel = replicated_compression_farm_kernel<LANES, ROUNDS, ADDRESS_BITS>;
186}
187
188#[kernel]
190pub fn replicated_compression_farm_kernel<
191 const LANES: usize,
192 const ROUNDS: usize,
193 const ADDRESS_BITS: usize,
194>(
195 clock_reset: ClockReset,
196 input: CompressionFarmInput<LANES>,
197 q: ReplicatedCompressionFarmQ<LANES, ROUNDS, ADDRESS_BITS>,
198) -> (
199 CompressionFarmOutput<LANES>,
200 ReplicatedCompressionFarmD<LANES, ROUNDS, ADDRESS_BITS>,
201)
202where
203 rhdl::bits::W<ROUNDS>: BitWidth,
204 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
205{
206 let _ = clock_reset;
207 let d = ReplicatedCompressionFarmD::<LANES, ROUNDS, ADDRESS_BITS> { lanes: input };
208 (q.lanes, d)
209}
210
211pub type ThreeLaneCompressionFarm =
218 ReplicatedCompressionFarm<PERFORMANCE_FARM_LANES, ROUND_COUNT, FULL_LANE_ADDRESS_BITS>;
219
220pub fn compression_farm_checked_verilog<
231 const LANES: usize,
232 const ROUNDS: usize,
233 const ADDRESS_BITS: usize,
234>() -> Result<String, RHDLError>
235where
236 rhdl::bits::W<ROUNDS>: BitWidth,
237 rhdl::bits::W<ADDRESS_BITS>: BitWidth,
238{
239 let farm = CompressionFarm::<LANES, ROUNDS, ADDRESS_BITS>::default();
240 Ok(farm
241 .descriptor("sha256_compression_farm".into())?
242 .hdl()?
243 .modules
244 .pretty())
245}
246
247pub fn try_full_three_lane_farm_generation_only_unchecked_verilog() -> Result<String, RHDLError> {
262 let farm = ThreeLaneCompressionFarm::default();
263 let descriptor = farm.descriptor("sha256_compression_farm_3x64".into())?;
264 let hdl = descriptor.hdl_unchecked()?;
265 Ok(hdl.modules.pretty())
266}
267
268#[cfg(test)]
269mod tests {
270 use rhdl::core::sim::ResetOrData;
271
272 use super::*;
273 use crate::{
274 ROUND_CONSTANTS_RHDL, RoundState, WorkingState, compress_block, feed_forward_kernel,
275 round_kernel,
276 };
277
278 const SMALL_LANES: usize = PERFORMANCE_FARM_LANES;
279 const SMALL_ROUNDS: usize = 4;
280 const SMALL_ADDRESS_BITS: usize = 2;
281
282 fn word(value: u32) -> b32 {
283 b32(u128::from(value))
284 }
285
286 fn word_u32(value: b32) -> u32 {
287 let bytes = value.raw().to_le_bytes();
288 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
289 }
290
291 fn prepared_input(lane: usize, marker: u32, sequence: u32) -> CompressionInput {
292 let lane = u32::try_from(lane).expect("the three lane indexes fit u32");
293 let chaining = core::array::from_fn(|index| {
294 let index = u32::try_from(index).expect("eight chaining words fit u32");
295 word(
296 0x1020_3040_u32
297 .wrapping_add(index.wrapping_mul(0x1111_1111))
298 .rotate_left(index)
299 ^ marker.rotate_right(index)
300 ^ lane.wrapping_mul(0x0101_0101),
301 )
302 });
303 let block = core::array::from_fn(|index| {
304 let index = u32::try_from(index).expect("sixteen block words fit u32");
305 word(
306 marker
307 .wrapping_mul(index.wrapping_add(1))
308 .rotate_left(index * 3)
309 ^ index.wrapping_mul(0x9e37_79b9)
310 ^ lane.rotate_left(index),
311 )
312 });
313 CompressionInput {
314 chaining,
315 block,
316 tag: word((lane << 24) | (sequence & 0x00ff_ffff)),
318 valid: true,
319 }
320 }
321
322 fn expected_output(input: &CompressionInput, rounds: usize) -> CompressionOutput {
323 let digest = if rounds == ROUND_COUNT {
324 let chaining = input.chaining.map(word_u32);
325 let block = input.block.map(word_u32);
326 compress_block(chaining, block).map(word)
327 } else {
328 let mut state = RoundState {
329 working: WorkingState {
330 a: input.chaining[0],
331 b: input.chaining[1],
332 c: input.chaining[2],
333 d: input.chaining[3],
334 e: input.chaining[4],
335 f: input.chaining[5],
336 g: input.chaining[6],
337 h: input.chaining[7],
338 },
339 schedule: input.block,
340 };
341 for round_constant in ROUND_CONSTANTS_RHDL.into_iter().take(rounds) {
342 state = round_kernel(state, round_constant);
343 }
344 feed_forward_kernel(input.chaining, state.working)
345 };
346 CompressionOutput {
347 digest,
348 tag: input.tag,
349 valid: true,
350 }
351 }
352
353 fn expected_lane_outputs<const LANES: usize>(
354 events: &[ResetOrData<CompressionFarmInput<LANES>>],
355 lane: usize,
356 rounds: usize,
357 ) -> Vec<(usize, CompressionOutput)> {
358 events
359 .iter()
360 .enumerate()
361 .filter_map(|(accepted_cycle, event)| {
362 let ResetOrData::Data(input) = event else {
363 return None;
364 };
365 let input = input[lane];
366 if !input.valid {
367 return None;
368 }
369 let output_cycle = accepted_cycle + rounds;
370 if output_cycle >= events.len()
371 || events[accepted_cycle + 1..output_cycle]
372 .iter()
373 .any(|event| matches!(event, ResetOrData::Reset))
374 {
375 return None;
376 }
377 Some((output_cycle, expected_output(&input, rounds)))
378 })
379 .collect()
380 }
381
382 fn observed_lane_outputs<const LANES: usize, Farm>(
383 farm: &Farm,
384 events: &[ResetOrData<CompressionFarmInput<LANES>>],
385 ) -> [Vec<(usize, CompressionOutput)>; LANES]
386 where
387 Farm: Synchronous<I = CompressionFarmInput<LANES>, O = CompressionFarmOutput<LANES>>,
388 {
389 let mut observed = core::array::from_fn(|_| Vec::new());
390 for (cycle, sample) in farm
391 .run(events.iter().copied().clock_pos_edge(10))
392 .synchronous_sample()
393 .enumerate()
394 {
395 for (lane, outputs) in observed.iter_mut().enumerate() {
396 if sample.output[lane].valid {
397 outputs.push((cycle, sample.output[lane]));
398 }
399 }
400 }
401 observed
402 }
403
404 fn all_valid_inputs(sequence: u32, marker: u32) -> CompressionFarmInput<SMALL_LANES> {
405 core::array::from_fn(|lane| {
406 prepared_input(
407 lane,
408 marker ^ (u32::try_from(lane).expect("lane fits u32") * 0x1111_1111),
409 sequence,
410 )
411 })
412 }
413
414 fn small_farm_events() -> Vec<ResetOrData<CompressionFarmInput<SMALL_LANES>>> {
415 let mut events = vec![ResetOrData::Reset];
416
417 events.extend(
420 (0..8_u32).map(|cycle| ResetOrData::Data(all_valid_inputs(cycle, 0x1000_0000 + cycle))),
421 );
422
423 events.extend([
426 ResetOrData::Data(all_valid_inputs(0x100, 0x2000_0001)),
427 ResetOrData::Data([
428 CompressionInput::default(),
429 prepared_input(1, 0x2100_0002, 0x101),
430 prepared_input(2, 0x2200_0002, 0x101),
431 ]),
432 ResetOrData::Data([
433 prepared_input(0, 0x2000_0003, 0x102),
434 CompressionInput::default(),
435 prepared_input(2, 0x2200_0003, 0x102),
436 ]),
437 ResetOrData::Data([
438 prepared_input(0, 0x2000_0004, 0x103),
439 prepared_input(1, 0x2100_0004, 0x103),
440 CompressionInput::default(),
441 ]),
442 ResetOrData::Reset,
443 ]);
444
445 for cycle in 0..17_u32 {
448 let input = core::array::from_fn(|lane| {
449 let valid = match lane {
450 0 => cycle % 5 != 1,
451 1 => cycle % 4 != 2,
452 2 => cycle % 3 != 0,
453 _ => false,
454 };
455 if valid {
456 prepared_input(
457 lane,
458 0x3000_0000
459 ^ cycle
460 ^ (u32::try_from(lane).expect("lane fits u32") * 0x0100_0000),
461 0x200 + cycle,
462 )
463 } else {
464 CompressionInput::default()
465 }
466 });
467 events.push(ResetOrData::Data(input));
468 }
469 events.extend(std::iter::repeat_n(
470 ResetOrData::Data([CompressionInput::default(); SMALL_LANES]),
471 SMALL_ROUNDS,
472 ));
473 events
474 }
475
476 #[test]
477 fn small_farm_rhdl_and_iverilog_preserve_asymmetric_lane_isolation() -> Result<(), RHDLError> {
478 let farm = CompressionFarm::<SMALL_LANES, SMALL_ROUNDS, SMALL_ADDRESS_BITS>::default();
479 let events = small_farm_events();
480 let observed = observed_lane_outputs(&farm, &events);
481
482 for (lane, lane_outputs) in observed.iter().enumerate() {
483 assert_eq!(
484 lane_outputs,
485 &expected_lane_outputs(&events, lane, SMALL_ROUNDS),
486 "lane {lane} changed latency, data, tag, or valid behavior"
487 );
488 assert!(lane_outputs.iter().all(|(_, output)| {
489 usize::try_from(word_u32(output.tag) >> 24).expect("lane byte fits usize") == lane
490 }));
491 }
492 assert_ne!(observed[0].len(), observed[2].len());
493 assert!(events.len() > 4 * SMALL_ROUNDS);
494
495 let test_bench = farm
496 .run(events.into_iter().clock_pos_edge(10))
497 .collect::<SynchronousTestBench<_, _>>();
498 test_bench
499 .rtl(&farm, &TestBenchOptions::default().skip(18))?
503 .run_iverilog()?;
504 Ok(())
505 }
506
507 #[test]
508 fn small_replicated_farm_matches_the_ordinary_farm_and_software_oracle() {
509 let ordinary = CompressionFarm::<SMALL_LANES, SMALL_ROUNDS, SMALL_ADDRESS_BITS>::default();
510 let replicated =
511 ReplicatedCompressionFarm::<SMALL_LANES, SMALL_ROUNDS, SMALL_ADDRESS_BITS>::default();
512 let events = small_farm_events();
513 let ordinary_observed = observed_lane_outputs(&ordinary, &events);
514 let replicated_observed = observed_lane_outputs(&replicated, &events);
515
516 assert_eq!(replicated_observed, ordinary_observed);
517 for (lane, lane_outputs) in replicated_observed.iter().enumerate() {
518 assert_eq!(
519 lane_outputs,
520 &expected_lane_outputs(&events, lane, SMALL_ROUNDS),
521 "replicated lane {lane} diverged from the independent round model"
522 );
523 }
524 }
525
526 #[test]
527 fn small_replicated_farm_lowers_one_lane_module_and_three_instances() -> Result<(), RHDLError> {
528 let farm =
529 ReplicatedCompressionFarm::<SMALL_LANES, SMALL_ROUNDS, SMALL_ADDRESS_BITS>::default();
530 let descriptor = farm.descriptor("sha256_replicated_compression_farm".into())?;
531 let hdl = descriptor.hdl()?.modules.pretty();
532
533 assert_eq!(
534 hdl.matches("module sha256_replicated_compression_farm_lanes_prototype(")
535 .count(),
536 1
537 );
538 for index in 0..SMALL_LANES {
539 assert_eq!(
540 hdl.matches(&format!(
541 "sha256_replicated_compression_farm_lanes_prototype c{index}("
542 ))
543 .count(),
544 1,
545 "replicated lane instance c{index} must appear exactly once"
546 );
547 }
548 assert_eq!(hdl.matches("reg [255:0] mem[3:0]").count(), 1);
549 Ok(())
550 }
551
552 #[test]
553 fn full_three_lane_farm_matches_software_for_more_than_two_wraps() {
554 std::thread::Builder::new()
559 .name("sha256-full-three-lane-rhdl".into())
560 .stack_size(64 * 1024 * 1024)
561 .spawn(run_full_three_lane_farm_test)
562 .expect("spawn full-farm RHDL test with an explicit stack")
563 .join()
564 .expect("full-farm RHDL test thread must not panic");
565 }
566
567 fn run_full_three_lane_farm_test() {
568 let mut events = vec![ResetOrData::Reset];
569 events.extend((0..7_u32).map(|cycle| {
570 ResetOrData::Data(core::array::from_fn(|lane| {
571 prepared_input(lane, 0x4000_0000 ^ cycle, cycle)
572 }))
573 }));
574 events.push(ResetOrData::Reset);
575
576 for cycle in 0..137_u32 {
579 let input = core::array::from_fn(|lane| {
580 let valid = match lane {
581 0 => cycle % 11 != 7,
582 1 => cycle % 7 != 3,
583 2 => cycle % 5 != 1,
584 _ => false,
585 };
586 if valid {
587 prepared_input(
588 lane,
589 0x5000_0000
590 ^ cycle.rotate_left(u32::try_from(lane).expect("lane fits u32")),
591 0x1000 + cycle,
592 )
593 } else {
594 CompressionInput::default()
595 }
596 });
597 events.push(ResetOrData::Data(input));
598 }
599 events.extend(std::iter::repeat_n(
600 ResetOrData::Data([CompressionInput::default(); PERFORMANCE_FARM_LANES]),
601 ROUND_COUNT,
602 ));
603
604 let farm = ThreeLaneCompressionFarm::default();
605 let observed = observed_lane_outputs(&farm, &events);
606 for (lane, lane_outputs) in observed.iter().enumerate() {
607 assert_eq!(
608 lane_outputs,
609 &expected_lane_outputs(&events, lane, ROUND_COUNT),
610 "full lane {lane} diverged from the independent SHA-256 model"
611 );
612 assert!(lane_outputs.len() > FULL_LANE_ADDRESS_BITS * 8);
613 assert!(lane_outputs.iter().all(|(_, output)| {
614 usize::try_from(word_u32(output.tag) >> 24).expect("lane byte fits usize") == lane
615 }));
616 }
617 }
618
619 #[test]
620 fn checked_small_farm_generator_contains_three_independent_memories() -> Result<(), RHDLError> {
621 let hdl =
622 compression_farm_checked_verilog::<SMALL_LANES, SMALL_ROUNDS, SMALL_ADDRESS_BITS>()?;
623 assert!(hdl.contains("module sha256_compression_farm"));
624 assert_eq!(hdl.matches("reg [255:0] mem[3:0]").count(), SMALL_LANES);
625 Ok(())
626 }
627
628 #[test]
629 #[ignore = "explicit full-hierarchy render gate; excluded from routine unit tests"]
630 fn full_replicated_farm_generation_only_unchecked_render() -> Result<(), RHDLError> {
631 let hdl = try_full_three_lane_farm_generation_only_unchecked_verilog()?;
632 assert!(hdl.contains("module sha256_compression_farm_3x64"));
633 assert_eq!(hdl.matches("reg [255:0] mem[63:0]").count(), 1);
634 assert_eq!(
635 hdl.matches("module sha256_compression_farm_3x64_lanes_prototype(")
636 .count(),
637 1
638 );
639 for index in 0..PERFORMANCE_FARM_LANES {
640 assert_eq!(
641 hdl.matches(&format!(
642 "sha256_compression_farm_3x64_lanes_prototype c{index}("
643 ))
644 .count(),
645 1
646 );
647 }
648 Ok(())
649 }
650}