1#![allow(clippy::collapsible_if)]
11
12use crate::{
13 multicomb::{MultiCombMul, MultiCombMulIn, MultiCombMulOut},
14 point_compress::{PointCompressPair, PointCompressPairIn, PointCompressPairOut},
15 sha512_parallel::{Sha512Pool, Sha512PoolIn, Sha512PoolOut},
16};
17use rhdl::prelude::*;
18use rhdl_fpga::{
19 core::dff::DFF,
20 fifo::synchronous::{In as FifoIn, SyncFIFO},
21};
22
23#[derive(Copy, Clone, Debug, PartialEq, Digital)]
24#[doc(hidden)]
25pub struct MergeIn<T: Digital> {
26 pub first: Option<T>,
27 pub second: Option<T>,
28}
29
30#[derive(Copy, Clone, Debug, PartialEq, Digital)]
31#[doc(hidden)]
32pub struct MergeOut<T: Digital> {
33 pub value: Option<T>,
34 pub lane: bool,
35 pub overflow: bool,
36}
37
38#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
40pub struct Merge2<T: Digital> {
41 first: SyncFIFO<T, 5>,
42 second: SyncFIFO<T, 5>,
43 prefer_second: DFF<bool>,
44}
45
46impl<T: Digital> Default for Merge2<T> {
47 fn default() -> Self {
48 Self {
49 first: SyncFIFO::default(),
50 second: SyncFIFO::default(),
51 prefer_second: DFF::new(false),
52 }
53 }
54}
55
56impl<T: Digital> SynchronousIO for Merge2<T> {
57 type I = MergeIn<T>;
58 type O = MergeOut<T>;
59 type Kernel = merge_kernel<T>;
60}
61
62#[kernel]
64pub fn merge_kernel<T: Digital>(
65 _cr: ClockReset,
66 i: MergeIn<T>,
67 q: Merge2Q<T>,
68) -> (MergeOut<T>, Merge2D<T>) {
69 let mut first = false;
70 let mut second = false;
71 let mut value = None;
72 if let Some(data) = q.first.data {
73 first = true;
74 value = Some(data);
75 }
76 if let Some(data) = q.second.data {
77 if !first || q.prefer_second {
78 first = false;
79 second = true;
80 value = Some(data);
81 }
82 }
83 let mut d = Merge2D::<T>::dont_care();
84 d.first = FifoIn::<T> {
85 data: i.first,
86 next: first,
87 };
88 d.second = FifoIn::<T> {
89 data: i.second,
90 next: second,
91 };
92 d.prefer_second = if first || second {
93 !second
94 } else {
95 q.prefer_second
96 };
97 (
98 MergeOut::<T> {
99 value,
100 lane: second,
101 overflow: q.first.overflow || q.second.overflow,
102 },
103 d,
104 )
105}
106
107#[derive(Copy, Clone, Debug, PartialEq, Digital)]
108#[doc(hidden)]
109pub struct Credits {
110 count: [b6; 2],
111 prefer_second: bool,
112}
113
114fn empty_credits() -> Credits {
115 Credits {
116 count: [b6(0); 2],
117 prefer_second: false,
118 }
119}
120
121#[kernel]
122fn select(credits: Credits, ready0: bool, ready1: bool) -> b2 {
123 let first = ready0 && credits.count[0] < b6(31);
124 let second = ready1 && credits.count[1] < b6(31);
125 if second && (!first || credits.prefer_second) {
126 b2(1)
127 } else if first {
128 b2(0)
129 } else {
130 b2(2)
131 }
132}
133
134#[kernel]
135fn update_credits(r: Credits, take: bool, lane: b2, retire: bool, retire_lane: bool) -> Credits {
136 let mut n = r;
137 if take {
138 n.count[lane] = n.count[lane] + b6(1);
139 n.prefer_second = lane == b2(0);
140 }
141 if retire {
142 let index = if retire_lane { b2(1) } else { b2(0) };
143 n.count[index] = n.count[index] - b6(1);
144 }
145 n
146}
147
148#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
151pub struct DualMultiComb {
152 first: MultiCombMul,
153 second: MultiCombMul,
154 results: Merge2<MultiCombMulOut>,
155 credits: DFF<Credits>,
156}
157
158impl Default for DualMultiComb {
159 fn default() -> Self {
160 Self {
161 first: MultiCombMul::default(),
162 second: MultiCombMul::default(),
163 results: Merge2::default(),
164 credits: DFF::new(empty_credits()),
165 }
166 }
167}
168
169impl SynchronousIO for DualMultiComb {
170 type I = MultiCombMulIn;
171 type O = MultiCombMulOut;
172 type Kernel = dual_multicomb_kernel;
173}
174
175#[kernel]
177pub fn dual_multicomb_kernel(
178 _cr: ClockReset,
179 i: MultiCombMulIn,
180 q: DualMultiCombQ,
181) -> (MultiCombMulOut, DualMultiCombD) {
182 let lane = select(q.credits, q.first.ready, q.second.ready);
183 let take = i.valid && lane != b2(2);
184 let mut o = q.first;
185 o.valid = false;
186 if let Some(value) = q.results.value {
187 o = value;
188 o.valid = true;
189 }
190 o.ready = lane != b2(2);
191 let occupied = q.credits.count[0] + q.credits.count[1];
192 o.occupancy = if occupied > b6(31) {
193 b5(31)
194 } else {
195 occupied.resize::<5>()
196 };
197 o.error = q.results.overflow;
198 let mut d = DualMultiCombD::dont_care();
199 d.first = i;
200 d.first.valid = take && lane == b2(0);
201 d.second = i;
202 d.second.valid = take && lane == b2(1);
203 d.results = MergeIn::<MultiCombMulOut> {
204 first: if q.first.valid { Some(q.first) } else { None },
205 second: if q.second.valid { Some(q.second) } else { None },
206 };
207 d.credits = update_credits(q.credits, take, lane, o.valid, q.results.lane);
208 (o, d)
209}
210
211#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
214pub struct DualPointCompress {
215 first: PointCompressPair,
216 second: PointCompressPair,
217 results: Merge2<PointCompressPairOut>,
218 credits: DFF<Credits>,
219}
220
221impl Default for DualPointCompress {
222 fn default() -> Self {
223 Self {
224 first: PointCompressPair::default(),
225 second: PointCompressPair::default(),
226 results: Merge2::default(),
227 credits: DFF::new(empty_credits()),
228 }
229 }
230}
231
232impl SynchronousIO for DualPointCompress {
233 type I = PointCompressPairIn;
234 type O = PointCompressPairOut;
235 type Kernel = dual_compress_kernel;
236}
237
238#[kernel]
240pub fn dual_compress_kernel(
241 _cr: ClockReset,
242 i: PointCompressPairIn,
243 q: DualPointCompressQ,
244) -> (PointCompressPairOut, DualPointCompressD) {
245 let lane = select(q.credits, q.first.ready, q.second.ready);
246 let take = i.valid && lane != b2(2);
247 let mut o = q.first;
248 o.valid = false;
249 if let Some(value) = q.results.value {
250 o = value;
251 o.valid = true;
252 }
253 o.ready = lane != b2(2);
254 let occupied = q.credits.count[0] + q.credits.count[1];
255 o.occupancy = if occupied > b6(15) {
256 b4(15)
257 } else {
258 occupied.resize::<4>()
259 };
260 o.error = q.results.overflow;
261 let mut d = DualPointCompressD::dont_care();
262 d.first = i;
263 d.first.valid = take && lane == b2(0);
264 d.second = i;
265 d.second.valid = take && lane == b2(1);
266 d.results = MergeIn::<PointCompressPairOut> {
267 first: if q.first.valid { Some(q.first) } else { None },
268 second: if q.second.valid { Some(q.second) } else { None },
269 };
270 d.credits = update_credits(q.credits, take, lane, o.valid, q.results.lane);
271 (o, d)
272}
273
274#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
279pub struct Sha512Pool8 {
280 first: Sha512Pool,
281 second: Sha512Pool,
282 prefer_second: DFF<bool>,
283}
284
285impl Default for Sha512Pool8 {
286 fn default() -> Self {
287 Self {
288 first: Sha512Pool::default(),
289 second: Sha512Pool::default(),
290 prefer_second: DFF::new(false),
291 }
292 }
293}
294
295impl SynchronousIO for Sha512Pool8 {
296 type I = Sha512PoolIn;
297 type O = Sha512PoolOut;
298 type Kernel = sha_pool8_kernel;
299}
300
301#[kernel]
303pub fn sha_pool8_kernel(
304 _cr: ClockReset,
305 i: Sha512PoolIn,
306 q: Sha512Pool8Q,
307) -> (Sha512PoolOut, Sha512Pool8D) {
308 let second = q.second.request0_ready && (!q.first.request0_ready || q.prefer_second);
309 let ready = q.first.request0_ready || q.second.request0_ready;
310 let take = i.request0.valid && ready;
311 let mut d = Sha512Pool8D::dont_care();
312 d.first = i;
313 d.first.request0.valid = take && !second;
314 d.first.request1.valid = false;
315 d.second = i;
316 d.second.request0.valid = take && second;
317 d.second.request1.valid = false;
318 d.prefer_second = if take { !second } else { q.prefer_second };
319 let mut o = q.first;
320 if q.second.result0.valid {
321 o.result0 = q.second.result0;
322 }
323 o.result1.valid = false;
324 o.request0_ready = ready;
325 o.request1_ready = false;
326 (o, d)
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::simulation::CycleSim;
333
334 #[test]
335 fn simultaneous_completions_are_lossless() {
336 let mut sim = CycleSim::new(Merge2::<b16>::default());
337 let mut seen = [false; 62];
338 for cycle in 0..130 {
339 let o = sim.tick(MergeIn {
340 first: (cycle < 31).then_some(b16(cycle)),
341 second: (cycle < 31).then_some(b16(cycle + 31)),
342 });
343 assert!(!o.overflow);
344 if let Some(value) = o.value {
345 let index = value.raw() as usize;
346 assert!(!seen[index], "duplicate {index}");
347 seen[index] = true;
348 }
349 }
350 assert!(seen.into_iter().all(|value| value));
351 }
352
353 #[test]
354 fn merge_and_sha_compile() -> miette::Result<()> {
355 rhdl::core::compile_design::<dual_multicomb_kernel>(
356 rhdl::core::CompilationMode::Synchronous,
357 )?;
358 rhdl::core::compile_design::<dual_compress_kernel>(
359 rhdl::core::CompilationMode::Synchronous,
360 )?;
361 Merge2::<b16>::default()
362 .descriptor(ScopedName::top())?
363 .hdl()?;
364 Sha512Pool8::default()
365 .descriptor(ScopedName::top())?
366 .hdl()?;
367 Ok(())
368 }
369
370 #[test]
371 fn eight_sha_lanes_keep_tags_and_results() {
372 use crate::sha512_parallel::Sha512PoolRequest;
373 let empty = Sha512PoolRequest {
374 valid: false,
375 tag: b9(0),
376 state: [b64(0); 8],
377 block: [b64(0); 16],
378 };
379 let mut sim = CycleSim::new(Sha512Pool8::default());
380 let mut o = sim.tick(Sha512PoolIn {
381 request0: empty,
382 request1: empty,
383 });
384 let mut accepted = 0;
385 let mut seen = [false; 16];
386 for _ in 0..600 {
387 let mut request = empty;
388 if o.request0_ready && accepted < 16 {
389 request.valid = true;
390 request.tag = b9(accepted);
391 accepted += 1;
392 }
393 o = sim.tick(Sha512PoolIn {
394 request0: request,
395 request1: empty,
396 });
397 assert!(!o.result1.valid);
398 if o.result0.valid {
399 let index = o.result0.tag.raw() as usize;
400 assert!(!seen[index]);
401 seen[index] = true;
402 }
403 }
404 assert_eq!(accepted, 16);
405 assert!(seen.into_iter().all(|value| value));
406 }
407}