1use rhdl_fpga_planner::{BuildFlags, Component, Context, Cost, Flow, Knob, Plan, Role, SHA512_BLOCKS, Share};
51
52pub use crate::folded::folded_lut;
53use crate::folded::FOLDS;
54
55pub const SHA_LINE_LUT: u32 = 65_000;
57pub const SHA_LINE_MAX_LUT: u32 = 432_000;
60pub const SHA_LINE_DSP: u32 = 20;
62pub const FULL_LUT: u32 = 62_252;
65pub const ROUTED_LUT: [(u32, u32); 8] =
68 [(2, 3_509), (4, 5_851), (5, 6_778), (8, 9_431), (10, 11_295), (16, 16_887), (20, 20_880), (40, 40_047)];
69pub const LARGEST_LINE_LUT: u32 = FULL_LUT;
72pub const LINE_LATENCY: f64 = crate::LATENCY as f64;
74
75#[must_use]
78pub fn cores() -> Vec<(u32, u32)> {
79 let routed = |s: u32| ROUTED_LUT.iter().find(|(t, _)| *t == s).map_or_else(|| folded_lut(s), |(_, l)| *l);
80 let mut c: Vec<(u32, u32)> = FOLDS.iter().map(|&s| (s, routed(s))).collect();
81 c.push((80, FULL_LUT));
82 c
83}
84
85#[derive(Clone, Debug, PartialEq, Eq, Default)]
87pub struct Line {
88 pub counts: Vec<u32>,
90}
91
92impl Line {
93 #[must_use]
95 pub fn units(&self) -> u32 {
96 cores().iter().zip(&self.counts).map(|((s, _), n)| s * n).sum()
97 }
98 #[must_use]
100 pub fn lut(&self) -> u32 {
101 cores().iter().zip(&self.counts).map(|((_, l), n)| l * n).sum()
102 }
103 #[must_use]
105 pub fn per_clock(&self) -> f64 {
106 f64::from(self.units()) / 80.0
107 }
108 #[must_use]
110 pub fn describe(&self) -> String {
111 let parts: Vec<String> = cores()
112 .iter()
113 .zip(&self.counts)
114 .rev()
115 .filter(|(_, n)| **n > 0)
116 .map(|((s, _), n)| if *s == 80 { format!("{n} full") } else { format!("{n} fold{s}") })
117 .collect();
118 if parts.is_empty() { "none".into() } else { parts.join(" + ") }
119 }
120}
121
122#[must_use]
125pub fn fastest(luts: u32) -> Line {
126 let kinds = cores();
127 let mut line = Line { counts: vec![0; kinds.len()] };
128 let best = kinds.iter().enumerate().filter(|(_, (_, l))| *l <= luts).max_by_key(|(_, (s, l))| (*s, std::cmp::Reverse(*l)));
129 if let Some((k, _)) = best {
130 line.counts[k] = 1;
131 }
132 line
133}
134
135#[must_use]
139pub fn smallest(units: u32) -> Line {
140 let kinds = cores();
141 let mut line = Line { counts: vec![0; kinds.len()] };
142 let pick = kinds.iter().enumerate().filter(|(_, (s, _))| *s >= units).min_by_key(|(_, (_, l))| *l);
143 let k = pick.map_or(kinds.len() - 1, |(k, _)| k);
144 line.counts[k] = 1;
145 line
146}
147
148#[derive(Clone, Debug)]
150pub struct Sha512 {
151 pub lut_cap: u32,
153 pub dsp_cap: u32,
155 pub auto: bool,
157}
158
159impl Default for Sha512 {
160 fn default() -> Self {
161 Self {
162 lut_cap: SHA_LINE_LUT,
163 dsp_cap: SHA_LINE_DSP,
164 auto: true,
165 }
166 }
167}
168
169const SIZE: [&str; 2] = ["auto (never limits)", "manual (LUT cap)"];
171
172impl Component for Sha512 {
173 fn name(&self) -> String {
174 "SHA-512 line".into()
175 }
176
177 fn role(&self) -> Role {
178 Role::Sha512
179 }
180
181 fn knobs(&self) -> Vec<Knob> {
182 vec![
183 Knob::range("LUT cap", f64::from(self.lut_cap), 0.0, f64::from(SHA_LINE_MAX_LUT), 1_000.0),
184 Knob::range("DSP cap", f64::from(self.dsp_cap), 0.0, f64::from(SHA_LINE_DSP), 1.0),
185 Knob::choice("size", usize::from(!self.auto), &SIZE),
186 ]
187 }
188
189 fn set_knob(&mut self, index: usize, value: f64) {
190 match index {
191 0 => {
193 self.lut_cap = (value.max(0.0) as u32).min(SHA_LINE_MAX_LUT);
194 self.auto = false;
195 }
196 1 => self.dsp_cap = (value.max(0.0) as u32).min(SHA_LINE_DSP),
197 _ => self.auto = value < 0.5,
198 }
199 }
200
201 fn plan(&self, share: &Share, cx: &Context) -> Option<Plan> {
202 if !self.auto && self.lut_cap == 0 {
206 return None;
207 }
208 let line = fastest(share.lut.min(LARGEST_LINE_LUT.max(self.lut_cap)));
209 if line.units() == 0 {
210 return None;
211 }
212 let per_clock = line.per_clock();
213 let rate = per_clock * cx.mhz * 1e6;
214 let size = if self.auto { "auto" } else { "manual" };
215 Some(Plan {
216 cost: Cost { lut: line.lut(), dsp: 0, bram36: 0, uram: 0 },
217 rate,
218 unit: "blocks",
219 limit: if !self.auto && share.lut > self.lut_cap {
220 format!("grew past its {}k allowance", self.lut_cap / 1_000)
221 } else {
222 format!("{size}, {}k", share.lut / 1_000)
223 },
224 detail: format!("{} = {per_clock:.4} blocks/clk ({size}; LUTs routed out of context, 4.878 ns)", line.describe()),
225 demands: Vec::new(),
226 supplies: vec![Flow { service: SHA512_BLOCKS, per_second: rate }],
227 measured: true,
229 latency_cycles: LINE_LATENCY,
230 in_flight: f64::from(line.units()) * LINE_LATENCY / 80.0,
232 batch: None,
233 covers: Vec::new(),
234 })
235 }
236
237 fn fixed(&self) -> Option<Share> {
238 Some(Share { lut: self.lut_cap, dsp: self.dsp_cap, bram36: 0, uram: 0 })
239 }
240
241 fn size_for(&self, per_second: f64, cx: &Context) -> Option<Share> {
242 if !self.auto {
243 return None;
244 }
245 let units = (per_second / (cx.mhz * 1e6) * 80.0 - 1e-9).ceil().max(0.0) as u32;
248 let lut = if units == 0 { 0 } else { smallest(units).lut() };
249 Some(Share { lut, dsp: 0, bram36: 0, uram: 0 })
250 }
251
252 fn resize(&mut self, allowance: Share) {
253 if self.auto {
254 self.lut_cap = allowance.lut;
255 }
256 }
257
258 fn build_flags(&self, plan: &Plan) -> BuildFlags {
259 let line = fastest(plan.cost.lut);
260 let mut flags: BuildFlags = vec![("sha512.cores".into(), format!("\"{}\"", line.describe()))];
261 for ((s, _), n) in cores().iter().zip(&line.counts) {
262 if *n > 0 {
263 let key = if *s == 80 { "sha512.full_pipelines".to_string() } else { format!("sha512.fold{s}") };
264 flags.push((key, n.to_string()));
265 }
266 }
267 flags
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn the_default_manual_line_is_above_half_a_block_per_clock() {
277 let sha = Sha512 { auto: false, ..Sha512::default() };
278 let plan = sha.plan(&sha.fixed().unwrap(), &Context::default()).unwrap();
279 assert!(plan.rate >= 0.5 * 205e6, "{}", plan.detail);
280 }
281
282 #[test]
283 fn shrinking_the_line_never_raises_its_rate() {
284 let cx = Context::default();
285 let mut last = f64::INFINITY;
286 for lut in (0..=SHA_LINE_LUT).rev().step_by(1_000) {
287 let sha = Sha512 { lut_cap: lut, auto: false, ..Sha512::default() };
288 let rate = sha.plan(&sha.fixed().unwrap(), &cx).map_or(0.0, |p| p.rate);
289 assert!(rate <= last);
290 last = rate;
291 }
292 }
293
294 #[test]
298 fn lines_are_one_core() {
299 for units in 1..=100 {
300 let line = smallest(units);
301 assert_eq!(line.counts.iter().sum::<u32>(), 1, "{units}: {}", line.describe());
302 if units <= 80 {
303 assert!(line.units() >= units, "{units}: {}", line.describe());
304 for (s, l) in cores() {
305 assert!(s < units || l >= line.lut(), "{units}: fold{s} is cheaper than {}", line.describe());
306 }
307 } else {
308 assert_eq!(line.units(), 80);
309 }
310 }
311 for lut in (0..=150_000).step_by(2_500) {
312 let line = fastest(lut);
313 assert!(line.lut() <= lut, "{lut}: {}", line.describe());
314 assert!(line.counts.iter().sum::<u32>() <= 1);
315 }
316 assert_eq!(fastest(3_000).units(), 0);
317 assert_eq!(fastest(LARGEST_LINE_LUT).units(), 80);
318 }
319
320 #[test]
323 fn auto_sizes_to_demand() {
324 let cx = Context::default();
325 let sha = Sha512::default();
326 let a = sha.size_for(25e6, &cx).unwrap();
328 let plan = sha.plan(&a, &cx).unwrap();
329 assert!(plan.rate >= 25e6 && plan.rate < 25e6 + 205e6 / 8.0, "{}", plan.detail);
330 assert!(a.lut < 20_000, "{a:?}");
331 assert_eq!(sha.size_for(0.0, &cx).unwrap().lut, 0);
332 assert!(Sha512 { auto: false, ..sha }.size_for(25e6, &cx).is_none());
333 }
334}