1use rhdl::prelude::*;
49use rhdl_fpga::core::{
50 dff::DFF,
51 ram::synchronous::{SyncBRAM, Write as RamWrite},
52};
53
54use crate::{
55 sha_ring::{ShaDone, ShaJob, ShaRingIn, ShaRingOut, pad_block_128},
56 sign_core::ram_read,
57};
58
59pub const CLIENT_CREDITS: usize = 32;
66pub const PIPE_LATENCY: usize = 82;
71
72#[derive(Copy, Clone, Debug, PartialEq, Digital)]
74pub struct PipeIn {
75 pub valid: bool,
78 pub tag: b16,
80 pub block: [b64; 16],
82 pub use_iv: bool,
84 pub chain: [b64; 8],
86}
87
88#[derive(Copy, Clone, Debug, PartialEq, Digital)]
90pub struct PipeOut {
91 pub valid: bool,
93 pub tag: b16,
95 pub state: [b64; 8],
98}
99
100#[kernel]
102pub fn idle_pipe_in() -> PipeIn {
103 PipeIn { valid: false, tag: b16(0), block: [b64(0); 16], use_iv: false, chain: [b64(0); 8] }
104}
105
106#[kernel]
108pub fn idle_pipe_out() -> PipeOut {
109 PipeOut { valid: false, tag: b16(0), state: [b64(0); 8] }
110}
111
112#[derive(Copy, Clone, Debug, PartialEq, Digital)]
114pub struct ShaClientIn {
115 pub ring: ShaRingIn,
117 pub grant: bool,
119 pub resp: PipeOut,
121}
122
123#[derive(Copy, Clone, Debug, PartialEq, Digital)]
125pub struct ShaClientOut {
126 pub ring: ShaRingOut,
128 pub req: PipeIn,
130}
131
132#[derive(Copy, Clone, Debug, PartialEq, Digital)]
134#[doc(hidden)]
135pub struct Entry {
136 tag: b16,
138 first: bool,
141 state: [b64; 8],
143}
144
145#[derive(Copy, Clone, Debug, PartialEq, Digital)]
147#[doc(hidden)]
148pub struct ClientRegs {
149 job: ShaJob,
152 credits: b6,
156 flags: [bool; 32],
158 flag_wr: b5,
160 flag_rd: b5,
162 wr: b5,
164 rd: b5,
166 count: b6,
168 fetching: bool,
170 head_valid: bool,
172 head: Entry,
175}
176
177#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
184pub struct ShaClient {
185 regs: DFF<ClientRegs>,
187 fifo: SyncBRAM<Entry, 5>,
189}
190
191impl Default for ShaClient {
193 fn default() -> Self {
195 let entry = Entry { tag: b16(0), first: false, state: [b64(0); 8] };
196 Self {
197 regs: DFF::new(ClientRegs {
198 job: ShaJob { valid: false, tag: b16(0), block: [b64(0); 16], second_pad: false },
199 credits: b6(CLIENT_CREDITS as u128),
200 flags: [false; 32],
201 flag_wr: b5(0),
202 flag_rd: b5(0),
203 wr: b5(0),
204 rd: b5(0),
205 count: b6(0),
206 fetching: false,
207 head_valid: false,
208 head: entry,
209 }),
210 fifo: SyncBRAM::default(),
211 }
212 }
213}
214
215impl SynchronousIO for ShaClient {
217 type I = ShaClientIn;
219 type O = ShaClientOut;
221 type Kernel = sha_client_kernel;
223}
224
225#[kernel]
227pub fn sha_client_kernel(_cr: ClockReset, i: ShaClientIn, q: ShaClientQ) -> (ShaClientOut, ShaClientD) {
228 let r = q.regs;
229 let mut n = r;
230 let mut d = ShaClientD::dont_care();
231 d.fifo = ram_read::<Entry, 5>(r.rd, q.fifo);
234
235 let mut count = r.count;
238 if r.fetching {
239 n.head = q.fifo;
240 n.head_valid = true;
241 n.fetching = false;
242 }
243 if !r.head_valid && !r.fetching && r.count != b6(0) {
245 n.fetching = true;
246 n.rd = r.rd + b5(1);
247 count = count - b6(1);
248 }
249
250 let head_pad = r.head_valid && r.head.first;
255 let head_done = r.head_valid && !r.head.first;
257 let mut req = idle_pipe_in();
258 let mut req_first = false;
261 if head_pad {
262 req = PipeIn { valid: true, tag: r.head.tag, block: pad_block_128(), use_iv: false, chain: r.head.state };
264 } else if r.job.valid && r.credits != b6(0) {
265 req = PipeIn { valid: true, tag: r.job.tag, block: r.job.block, use_iv: true, chain: [b64(0); 8] };
267 req_first = r.job.second_pad;
268 }
269 let mut credits = r.credits;
270 if req.valid && i.grant {
271 n.flags[r.flag_wr] = req_first;
274 n.flag_wr = r.flag_wr + b5(1);
275 if head_pad {
276 n.head_valid = false;
279 } else {
280 n.job.valid = false;
281 credits = credits - b6(1);
282 }
283 }
284
285 if head_done && i.ring.out_ready {
287 n.head_valid = false;
288 credits = credits + b6(1);
289 }
290
291 if i.ring.job.valid && !r.job.valid {
293 n.job = i.ring.job;
294 }
295
296 if i.resp.valid {
299 d.fifo.write = RamWrite::<Entry, 5> {
300 addr: r.wr,
301 value: Entry { tag: i.resp.tag, first: r.flags[r.flag_rd], state: i.resp.state },
302 enable: true,
303 };
304 n.wr = r.wr + b5(1);
305 n.flag_rd = r.flag_rd + b5(1);
306 count = count + b6(1);
307 }
308 n.count = count;
309 n.credits = credits;
310 d.regs = n;
311 let out = ShaClientOut {
312 ring: ShaRingOut {
313 ready: !r.job.valid,
314 done: ShaDone { valid: head_done, tag: r.head.tag, state: r.head.state },
315 },
316 req: req,
317 };
318 (out, d)
319}
320
321#[derive(Copy, Clone, Debug, PartialEq, Digital)]
323pub struct ArbiterIn<const N: usize> {
324 pub reqs: [PipeIn; N],
326 pub pipe: PipeOut,
328 pub pipe_ready: bool,
332}
333
334#[derive(Copy, Clone, Debug, PartialEq, Digital)]
336pub struct ArbiterOut<const N: usize> {
337 pub grants: [bool; N],
339 pub resps: [PipeOut; N],
341 pub pipe: PipeIn,
343}
344
345#[derive(Copy, Clone, Debug, PartialEq, Digital)]
347#[doc(hidden)]
348pub struct ArbiterRegs {
349 next: b4,
351 owner: [b4; 128],
353 wr: b7,
355 rd: b7,
357}
358
359#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
363pub struct ShaArbiter<const N: usize> {
364 regs: DFF<ArbiterRegs>,
366}
367
368impl<const N: usize> Default for ShaArbiter<N> {
371 fn default() -> Self {
373 assert!(N >= 1 && N <= 16 && PIPE_LATENCY < 128);
374 Self { regs: DFF::new(ArbiterRegs { next: b4(0), owner: [b4(0); 128], wr: b7(0), rd: b7(0) }) }
375 }
376}
377
378impl<const N: usize> SynchronousIO for ShaArbiter<N> {
380 type I = ArbiterIn<N>;
382 type O = ArbiterOut<N>;
384 type Kernel = sha_arbiter_kernel<N>;
386}
387
388#[kernel]
390pub fn sha_arbiter_kernel<const N: usize>(_cr: ClockReset, i: ArbiterIn<N>, q: ShaArbiterQ<N>) -> (ArbiterOut<N>, ShaArbiterD<N>) {
391 let r = q.regs;
392 let mut n = r;
393 let mut d = ShaArbiterD::<N>::dont_care();
394 let mut found = false;
397 let mut pick = b4(0);
398 for k in 0..N {
399 if !found && i.pipe_ready && i.reqs[k].valid && b4(k as u128) >= r.next {
400 found = true;
401 pick = b4(k as u128);
402 }
403 }
404 for k in 0..N {
405 if !found && i.pipe_ready && i.reqs[k].valid {
406 found = true;
407 pick = b4(k as u128);
408 }
409 }
410 let mut grants = [false; N];
411 let mut pipe = idle_pipe_in();
412 for k in 0..N {
413 if found && pick == b4(k as u128) {
414 grants[k] = true;
415 pipe = i.reqs[k];
416 }
417 }
418 if found {
421 n.next = if pick == b4((N - 1) as u128) { b4(0) } else { pick + b4(1) };
422 }
423 if found {
427 n.owner[r.wr] = pick;
428 n.wr = r.wr + b7(1);
429 }
430 let owner = r.owner[r.rd];
431 if i.pipe.valid {
432 n.rd = r.rd + b7(1);
433 }
434 let mut resps = [idle_pipe_out(); N];
436 for k in 0..N {
437 if i.pipe.valid && owner == b4(k as u128) {
438 resps[k] = i.pipe;
439 }
440 }
441 d.regs = n;
442 (ArbiterOut::<N> { grants: grants, resps: resps, pipe: pipe }, d)
443}
444
445#[derive(Copy, Clone, Debug, PartialEq, Digital)]
447pub struct SharedShaIn<const N: usize> {
448 pub rings: [ShaRingIn; N],
450 pub pipe: PipeOut,
452 pub pipe_ready: bool,
455}
456
457#[derive(Copy, Clone, Debug, PartialEq, Digital)]
459pub struct SharedShaOut<const N: usize> {
460 pub rings: [ShaRingOut; N],
462 pub pipe: PipeIn,
464}
465
466#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
469pub struct SharedShaPort<const N: usize> {
470 clients: [ShaClient; N],
472 arbiter: ShaArbiter<N>,
474}
475
476impl<const N: usize> Default for SharedShaPort<N> {
478 fn default() -> Self {
480 Self { clients: std::array::from_fn(|_| ShaClient::default()), arbiter: ShaArbiter::default() }
481 }
482}
483
484impl<const N: usize> SynchronousIO for SharedShaPort<N> {
486 type I = SharedShaIn<N>;
488 type O = SharedShaOut<N>;
490 type Kernel = shared_sha_port_kernel<N>;
492}
493
494#[kernel]
500pub fn shared_sha_port_kernel<const N: usize>(_cr: ClockReset, i: SharedShaIn<N>, q: SharedShaPortQ<N>) -> (SharedShaOut<N>, SharedShaPortD<N>) {
501 let mut d = SharedShaPortD::<N>::dont_care();
502 let mut reqs = [idle_pipe_in(); N];
503 let mut rings = [q.clients[0].ring; N];
506 for k in 0..N {
507 reqs[k] = q.clients[k].req;
508 rings[k] = q.clients[k].ring;
509 }
510 d.arbiter = ArbiterIn::<N> { reqs: reqs, pipe: i.pipe, pipe_ready: i.pipe_ready };
511 for k in 0..N {
512 d.clients[k] = ShaClientIn { ring: i.rings[k], grant: q.arbiter.grants[k], resp: q.arbiter.resps[k] };
513 }
514 (SharedShaOut::<N> { rings: rings, pipe: q.arbiter.pipe }, d)
515}
516
517#[cfg(test)]
520pub(crate) mod tests {
521 use super::*;
522 use crate::simulation::CycleSim;
523 use std::collections::VecDeque;
524
525 const K: [u64; 80] = [
527 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
528 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
529 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
530 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
531 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
532 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
533 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
534 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
535 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
536 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
537 ];
538 pub(crate) const IV: [u64; 8] = [
540 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179,
541 ];
542
543 pub fn compress(chain: [u64; 8], block: [u64; 16]) -> [u64; 8] {
545 let mut w = [0u64; 80];
546 w[..16].copy_from_slice(&block);
547 for t in 16..80 {
548 let s0 = w[t - 15].rotate_right(1) ^ w[t - 15].rotate_right(8) ^ (w[t - 15] >> 7);
549 let s1 = w[t - 2].rotate_right(19) ^ w[t - 2].rotate_right(61) ^ (w[t - 2] >> 6);
550 w[t] = w[t - 16].wrapping_add(s0).wrapping_add(w[t - 7]).wrapping_add(s1);
551 }
552 let mut v = chain;
553 for t in 0..80 {
554 let [a, b, c, dd, e, f, g, h] = v;
555 let t1 = h
556 .wrapping_add(e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41))
557 .wrapping_add((e & f) ^ (!e & g))
558 .wrapping_add(K[t])
559 .wrapping_add(w[t]);
560 let t2 = (a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39)).wrapping_add((a & b) ^ (a & c) ^ (b & c));
561 v = [t1.wrapping_add(t2), a, b, c, dd.wrapping_add(t1), e, f, g];
562 }
563 std::array::from_fn(|k| chain[k].wrapping_add(v[k]))
564 }
565
566 fn words<const M: usize>(x: [b64; M]) -> [u64; M] {
568 x.map(|w| w.raw() as u64)
569 }
570
571 pub(crate) struct Pipe {
573 line: VecDeque<Option<PipeOut>>,
575 }
576
577 impl Pipe {
579 pub(crate) fn new() -> Self {
581 Pipe { line: (0..PIPE_LATENCY).map(|_| None).collect() }
582 }
583 pub(crate) fn step(&mut self, req: PipeIn) -> PipeOut {
585 let out = self.line.pop_front().unwrap();
586 let next = req.valid.then(|| {
587 let chain = if req.use_iv { IV } else { words(req.chain) };
588 PipeOut { valid: true, tag: req.tag, state: compress(chain, words(req.block)).map(|w| b64(w as u128)) }
589 });
590 self.line.push_back(next);
591 out.unwrap_or(PipeOut { valid: false, tag: b16(0), state: [b64(0); 8] })
592 }
593 }
594
595 pub(crate) trait Line {
600 fn ready(&self) -> bool;
602 fn out(&mut self) -> PipeOut;
604 fn take(&mut self, req: PipeIn);
606 fn held(&self) -> usize {
608 0
609 }
610 }
611
612 impl Line for Pipe {
614 fn ready(&self) -> bool {
615 true
616 }
617 fn out(&mut self) -> PipeOut {
618 self.line.pop_front().unwrap().unwrap_or(PipeOut { valid: false, tag: b16(0), state: [b64(0); 8] })
619 }
620 fn take(&mut self, req: PipeIn) {
621 let next = req.valid.then(|| {
622 let chain = if req.use_iv { IV } else { words(req.chain) };
623 PipeOut { valid: true, tag: req.tag, state: compress(chain, words(req.block)).map(|w| b64(w as u128)) }
624 });
625 self.line.push_back(next);
626 }
627 }
628
629 pub(crate) struct Folded {
634 cap: usize,
635 interval: u64,
636 now: u64,
637 last: u64,
638 held: VecDeque<(u64, PipeOut)>,
639 }
640
641 impl Folded {
643 pub(crate) fn new(stages: usize) -> Self {
645 Folded { cap: stages + 1, interval: 80_u64.div_ceil(stages as u64), now: 0, last: 0, held: VecDeque::new() }
646 }
647 }
648
649 impl Line for Folded {
651 fn ready(&self) -> bool {
652 self.held.len() < self.cap
653 }
654 fn out(&mut self) -> PipeOut {
655 self.now += 1;
656 if let Some(&(at, result)) = self.held.front() {
657 if at <= self.now && self.now >= self.last + self.interval {
658 self.held.pop_front();
659 self.last = self.now;
660 return result;
661 }
662 }
663 PipeOut { valid: false, tag: b16(0), state: [b64(0); 8] }
664 }
665 fn held(&self) -> usize {
666 self.held.len()
667 }
668 fn take(&mut self, req: PipeIn) {
669 if req.valid {
670 assert!(self.held.len() < self.cap, "block presented while the folded core was not ready");
671 let chain = if req.use_iv { IV } else { words(req.chain) };
672 let state = compress(chain, words(req.block)).map(|w| b64(w as u128));
673 self.held.push_back((self.now + 82, PipeOut { valid: true, tag: req.tag, state }));
674 }
675 }
676 }
677
678 fn job(k: u64, two: bool) -> ([u64; 16], bool) {
681 let mut block = [0u64; 16];
682 for (j, w) in block.iter_mut().enumerate() {
683 *w = (k + 1).wrapping_mul(0x9e3779b97f4a7c15).rotate_left(j as u32 * 7) ^ j as u64;
684 }
685 (block, two)
686 }
687
688 fn expected(block: [u64; 16], two: bool) -> [u64; 8] {
691 let s = compress(IV, block);
692 if two {
693 let mut pad = [0u64; 16];
694 pad[0] = 0x8000000000000000;
695 pad[15] = 0x400;
696 compress(s, pad)
697 } else {
698 s
699 }
700 }
701
702 #[test]
706 fn clients_share_one_pipeline() {
707 share(&mut Pipe::new(), 40_000);
708 }
709
710 #[test]
714 fn clients_share_a_folded_core() {
715 for stages in [2, 10, 40] {
716 share(&mut Folded::new(stages), 400_000);
717 }
718 }
719
720 fn share(line: &mut dyn Line, limit: u64) {
723 const N: usize = 3;
725 let mut sim = CycleSim::new(SharedShaPort::<N>::default());
726 let pipe = line;
727 let jobs_per_client = 120u64;
728 let mut sent = [0u64; N];
729 let mut got: Vec<Vec<(u64, [u64; 8])>> = vec![Vec::new(); N];
730 let idle_ring = ShaRingIn { job: ShaJob { valid: false, tag: b16(0), block: [b64(0); 16], second_pad: false }, out_ready: false };
731 let mut out = sim.tick(SharedShaIn::<N> { rings: [idle_ring; N], pipe: idle_pipe_out_value(), pipe_ready: false });
732 let mut cycle = 0u64;
733 while got.iter().map(Vec::len).sum::<usize>() < N * jobs_per_client as usize {
734 cycle += 1;
735 assert!(cycle < limit, "stalled: {:?}", got.iter().map(Vec::len).collect::<Vec<_>>());
736 let ready = pipe.ready();
740 let pipe_out = pipe.out();
741 let mut rings = [idle_ring; N];
742 for k in 0..N {
743 if sent[k] < jobs_per_client && out.rings[k].ready && (cycle + k as u64) % (k as u64 + 1) == 0 {
746 let id = sent[k];
747 let (block, two) = job(id * 7 + k as u64, (id + k as u64) % 2 == 0);
748 rings[k].job = ShaJob { valid: true, tag: b16(id as u128), block: block.map(|w| b64(w as u128)), second_pad: two };
749 sent[k] += 1;
750 }
751 rings[k].out_ready = k != 2 || cycle % 5 == 0;
753 if out.rings[k].done.valid && rings[k].out_ready {
754 got[k].push((out.rings[k].done.tag.raw() as u64, words(out.rings[k].done.state)));
755 }
756 }
757 let (before, after) = sim.tick_both(SharedShaIn::<N> { rings, pipe: pipe_out, pipe_ready: ready });
758 pipe.take(before.pipe);
759 out = after;
760 }
761 for k in 0..N {
762 for (tag, state) in &got[k] {
763 let (block, two) = job(tag * 7 + k as u64, (tag + k as u64) % 2 == 0);
764 assert_eq!(*state, expected(block, two), "client {k} tag {tag}");
765 }
766 let mut tags: Vec<u64> = got[k].iter().map(|g| g.0).collect();
767 tags.sort();
768 assert_eq!(tags, (0..jobs_per_client).collect::<Vec<_>>());
769 }
770 println!("shared SHA: {} blocks in {cycle} cycles", N as u64 * jobs_per_client * 3 / 2);
771 }
772
773 pub(crate) fn idle_out() -> PipeOut {
775 idle_pipe_out_value()
776 }
777
778 fn idle_pipe_out_value() -> PipeOut {
780 PipeOut { valid: false, tag: b16(0), state: [b64(0); 8] }
781 }
782
783 #[test]
785 fn reference_compress_matches_sha2() {
786 use sha2::{Digest, Sha512};
787 let msg = [0x61u8, 0x62, 0x63];
788 let mut block = [0u64; 16];
789 block[0] = 0x6162638000000000;
790 block[15] = 24;
791 let want = Sha512::digest(msg);
792 let got: Vec<u8> = compress(IV, block).iter().flat_map(|w| w.to_be_bytes()).collect();
793 assert_eq!(&got[..], &want[..]);
794 }
795}
796