1use crate::*;
5
6#[derive(Clone, Debug, PartialEq)]
8pub struct ChipPlan {
9 pub parts: Vec<(Share, Option<Plan>)>,
13 pub guaranteed: Vec<Share>,
15 pub total: Cost,
17 pub delivered: Delivered,
19}
20
21pub fn plan_slot(slot: &Slot, share: &Share, cx: &Context) -> Option<Plan> {
28 use std::{cell::RefCell, collections::HashMap};
29 thread_local! {
30 static CACHE: RefCell<HashMap<String, Option<Plan>>> = RefCell::new(HashMap::new());
31 }
32 let c = &slot.component;
33 let knobs: Vec<f64> = c.knobs().iter().map(|k| k.value).collect();
34 let key = format!("{}|{knobs:?}|{share:?}|{}|{}", c.name(), cx.mhz, cx.best_case);
35 if let Some(hit) = CACHE.with(|m| m.borrow().get(&key).cloned()) {
36 return hit;
37 }
38 let plan = c.plan(share, cx);
39 CACHE.with(|m| {
40 let mut m = m.borrow_mut();
41 if m.len() > 200_000 {
42 m.clear();
43 }
44 m.insert(key, plan.clone());
45 });
46 plan
47}
48
49pub(crate) fn free_pool(budget: &Budget, parts: &[(Share, Option<Plan>)]) -> Share {
53 let mut free = budget.chip();
54 for (_, plan) in parts {
55 if let Some(p) = plan {
56 free = Share {
57 lut: free.lut.saturating_sub(p.cost.lut),
58 dsp: free.dsp.saturating_sub(p.cost.dsp),
59 bram36: free.bram36.saturating_sub(p.cost.bram36),
60 uram: free.uram.saturating_sub(p.cost.uram),
61 };
62 }
63 }
64 free
65}
66
67pub(crate) fn gain(old: &Delivered, new: &Delivered) -> f64 {
70 Side::BOTH
71 .iter()
72 .map(|&s| {
73 let (a, b) = (old.side(s).rate, new.side(s).rate);
74 if a <= 0.0 { if b > 0.0 { 1.0 } else { 0.0 } } else { (b - a) / a }
75 })
76 .sum()
77}
78
79pub fn plan_chip(mode: Mode, budget: &Budget, cx: &Context, slots: &[Slot]) -> ChipPlan {
107 let mut allow = allowances_in(mode, slots);
108 let auto: Vec<usize> = (0..slots.len())
109 .filter(|&k| mode.admits(&*slots[k].component) && slots[k].component.size_for(0.0, cx).is_some())
110 .collect();
111 let mut plan = plan_with(mode, budget, cx, slots, &allow);
112 floor_starved(mode, budget, cx, slots, &mut allow, &mut plan);
113 if auto.is_empty() {
114 return plan;
115 }
116 let rank = |p: &ChipPlan| (!p.delivered.sha_bound, p.delivered.sign.rate.min(p.delivered.verify.rate), p.delivered.sign.rate + p.delivered.verify.rate);
117 let mut best = plan.clone();
118 for _ in 0..4 {
119 let want = plan.delivered.sha_wanted / auto.len() as f64;
121 let mut changed = false;
122 for &k in &auto {
123 let a = slots[k].component.size_for(want, cx);
124 if a != allow[k] {
125 allow[k] = a;
126 changed = true;
127 }
128 }
129 if !changed {
130 break;
131 }
132 plan = plan_with(mode, budget, cx, slots, &allow);
133 if rank(&plan).partial_cmp(&rank(&best)) == Some(std::cmp::Ordering::Greater) {
134 best = plan.clone();
135 }
136 }
137 best
138}
139
140pub(crate) fn floor_starved(mode: Mode, budget: &Budget, cx: &Context, slots: &[Slot], allow: &mut [Option<Share>], plan: &mut ChipPlan) {
151 let rank = |p: &ChipPlan| (p.delivered.sign.rate.min(p.delivered.verify.rate), p.delivered.sign.rate + p.delivered.verify.rate);
152 let starved = |k: usize, plan: &ChipPlan, allow: &[Option<Share>]| {
153 let slot = &slots[k];
154 mode.admits(&*slot.component)
155 && allow[k].is_none()
156 && slot.power > 0.0
157 && slot.limits.iter().all(Option::is_none)
158 && plan.parts[k].1.is_none()
159 && slot.component.role().side().is_some_and(|side| plan.delivered.side(side).rate <= 0.0)
160 };
161 for side in [Side::Sign, Side::Verify] {
166 let group: Vec<usize> = (0..slots.len())
167 .filter(|&k| starved(k, plan, allow) && slots[k].component.role().side() == Some(side))
168 .collect();
169 if group.is_empty() {
170 continue;
171 }
172 let mut trial_allow = allow.to_vec();
173 let mut ok = true;
174 for &k in &group {
175 match smallest_build(&slots[k], &budget.rest_with(&trial_allow), cx) {
176 Some(floor) => trial_allow[k] = Some(floor),
177 None => ok = false,
178 }
179 }
180 if !ok {
181 continue;
182 }
183 let trial = plan_with(mode, budget, cx, slots, &trial_allow);
184 if rank(&trial) > rank(plan) {
185 allow.copy_from_slice(&trial_allow);
186 *plan = trial;
187 }
188 }
189}
190
191pub(crate) fn smallest_build(slot: &Slot, rest: &Share, cx: &Context) -> Option<Share> {
195 let at = |t: f64| Share {
196 lut: (rest.lut as f64 * t) as u32,
197 dsp: (rest.dsp as f64 * t) as u32,
198 bram36: (rest.bram36 as f64 * t) as u32,
199 uram: (rest.uram as f64 * t) as u32,
200 };
201 plan_slot(slot, rest, cx)?;
202 let (mut lo, mut hi) = (0.0, 1.0);
203 for _ in 0..12 {
204 let mid = (lo + hi) / 2.0;
205 if plan_slot(slot, &at(mid), cx).is_some() { hi = mid } else { lo = mid }
206 }
207 plan_slot(slot, &at(hi), cx).map(|p| p.cost)
208}
209
210pub(crate) fn plan_with(mode: Mode, budget: &Budget, cx: &Context, slots: &[Slot], allow: &[Option<Share>]) -> ChipPlan {
212 let guaranteed = shares_with(budget, slots, allow);
213 let on: Vec<bool> =
214 slots.iter().map(|s| mode.admits(&*s.component) && (s.component.fixed().is_some() || s.power > 0.0)).collect();
215 let mut parts: Vec<(Share, Option<Plan>)> = slots
216 .iter()
217 .zip(&guaranteed)
218 .zip(&on)
219 .map(|((slot, share), &on)| (*share, if on { plan_slot(slot, share, cx) } else { None }))
220 .collect();
221 let mut delivered = deliver(mode, cx, slots, &parts);
222 let side_free = |side: Side, parts: &[(Share, Option<Plan>)]| {
224 let mut left = Share::default();
225 for k in 0..slots.len() {
226 if !on[k] || slots[k].component.fixed().is_some() || slots[k].component.role().side() != Some(side) {
227 continue;
228 }
229 let g = guaranteed[k];
230 left = left.add(g, 1);
231 }
232 for k in 0..slots.len() {
233 if !on[k] || slots[k].component.fixed().is_some() || slots[k].component.role().side() != Some(side) {
234 continue;
235 }
236 if let Some(p) = &parts[k].1 {
237 left = Share {
238 lut: left.lut.saturating_sub(p.cost.lut),
239 dsp: left.dsp.saturating_sub(p.cost.dsp),
240 bram36: left.bram36.saturating_sub(p.cost.bram36),
241 uram: left.uram.saturating_sub(p.cost.uram),
242 };
243 }
244 }
245 left
246 };
247 for phase in 0..2 {
251 for _ in 0..8 {
252 let global = free_pool(budget, &parts);
253 if global == Share::default() {
254 break;
255 }
256 let mut best: Option<(f64, usize, Share, Plan, Delivered)> = None;
257 for k in 0..slots.len() {
258 if !on[k] || (phase == 0 && slots[k].component.fixed().is_some()) {
261 continue;
262 }
263 let free = match (phase, slots[k].component.role().side()) {
264 (0, Some(side)) => {
265 let f = side_free(side, &parts);
266 Share { lut: f.lut.min(global.lut), dsp: f.dsp.min(global.dsp), bram36: f.bram36.min(global.bram36), uram: f.uram.min(global.uram) }
267 }
268 _ => global,
269 };
270 if free == Share::default() {
271 continue;
272 }
273 let base = parts[k].1.as_ref().map_or(Share::default(), |p| p.cost);
278 let mut wider = Share {
279 lut: base.lut + free.lut,
280 dsp: base.dsp + free.dsp,
281 bram36: base.bram36 + free.bram36,
282 uram: base.uram + free.uram,
283 };
284 for (i, r) in Resource::ALL.into_iter().enumerate() {
286 if slots[k].limits[i].is_some() {
287 wider = r.with(wider, r.of(&guaranteed[k]));
288 }
289 }
290 if wider == base {
291 continue;
292 }
293 let Some(p) = plan_slot(&slots[k], &wider, cx) else { continue };
294 if parts[k].1.as_ref().is_some_and(|q| p.rate <= q.rate * (1.0 + 1e-9)) {
295 continue;
296 }
297 let mut trial = parts.clone();
298 trial[k] = (wider, Some(p.clone()));
299 let d = deliver(mode, cx, slots, &trial);
300 let g = gain(&delivered, &d);
301 if g > 1e-6 && best.as_ref().is_none_or(|b| g > b.0) {
302 best = Some((g, k, wider, p, d));
303 }
304 }
305 let Some((g, k, mut wider, mut p, mut d)) = best else { break };
306 let base = parts[k].1.as_ref().map_or(Share::default(), |q| q.cost);
310 let room = Share { lut: wider.lut - base.lut, dsp: wider.dsp - base.dsp, bram36: wider.bram36 - base.bram36, uram: wider.uram - base.uram };
311 for div in [16u32, 8, 4, 2] {
312 let small = Share {
313 lut: base.lut + room.lut / div,
314 dsp: base.dsp + room.dsp / div,
315 bram36: base.bram36 + room.bram36 / div,
316 uram: base.uram + room.uram / div,
317 };
318 let Some(q) = plan_slot(&slots[k], &small, cx) else { continue };
319 let mut trial = parts.clone();
320 trial[k] = (small, Some(q.clone()));
321 let e = deliver(mode, cx, slots, &trial);
322 if gain(&delivered, &e) >= 0.999 * g {
323 (wider, p, d) = (small, q, e);
324 break;
325 }
326 }
327 parts[k] = (wider, Some(p));
328 delivered = d;
329 }
330 }
331 fill_remainder(mode, budget, cx, slots, &on, &guaranteed, &mut parts, &mut delivered);
332 let total = parts.iter().filter_map(|(_, p)| p.as_ref()).fold(Cost::default(), |a, p| a.add(p.cost, 1));
333 ChipPlan { parts, guaranteed, total, delivered }
334}
335
336pub(crate) fn widen(slot: &Slot, guaranteed: &Share, share: Share, extra: Share) -> Share {
338 let mut wider = share.add(extra, 1);
339 for (i, r) in Resource::ALL.into_iter().enumerate() {
340 if slot.limits[i].is_some() {
341 wider = r.with(wider, r.of(guaranteed));
342 }
343 }
344 wider
345}
346
347pub(crate) fn no_side_drops(old: &Delivered, new: &Delivered) -> bool {
349 Side::BOTH.iter().all(|&s| new.side(s).rate >= old.side(s).rate * (1.0 - 1e-9))
350}
351
352#[allow(clippy::too_many_arguments)]
372pub(crate) fn fill_remainder(
373 mode: Mode,
374 budget: &Budget,
375 cx: &Context,
376 slots: &[Slot],
377 on: &[bool],
378 guaranteed: &[Share],
379 parts: &mut [(Share, Option<Plan>)],
380 delivered: &mut Delivered,
381) {
382 let mut order: Vec<usize> =
383 (0..slots.len()).filter(|&k| on[k] && (slots[k].component.fixed().is_some() || slots[k].power > 0.0)).collect();
384 order.sort_by(|&a, &b| guaranteed[b].lut.cmp(&guaranteed[a].lut));
385 let mut total = Share::default();
388 for &k in &order {
389 total = total.add(guaranteed[k], 1);
390 }
391 let part = |free: u32, mine: u32, all: u32| if all == 0 { 0 } else { (free as f64 * mine as f64 / all as f64) as u32 };
392 let try_grow = |k: usize, share: Share, parts: &mut [(Share, Option<Plan>)], delivered: &mut Delivered| -> bool {
394 let Some(p) = plan_slot(&slots[k], &share, cx) else { return false };
395 if parts[k].1.as_ref().is_some_and(|q| p.rate <= q.rate * (1.0 + 1e-9)) {
396 return false;
397 }
398 let old = std::mem::replace(&mut parts[k], (share, Some(p)));
399 let d = deliver(mode, cx, slots, parts);
400 if no_side_drops(delivered, &d) {
401 *delivered = d;
402 true
403 } else {
404 parts[k] = old;
405 false
406 }
407 };
408 for _ in 0..6 {
409 let free = free_pool(budget, parts);
410 if free == Share::default() {
411 return;
412 }
413 let mut grew = false;
414 for &k in &order {
415 let g = guaranteed[k];
416 let slice = Share {
417 lut: part(free.lut, g.lut, total.lut),
418 dsp: part(free.dsp, g.dsp, total.dsp),
419 bram36: part(free.bram36, g.bram36, total.bram36),
420 uram: part(free.uram, g.uram, total.uram),
421 };
422 let base = parts[k].1.as_ref().map_or(Share::default(), |p| p.cost);
423 let wider = widen(&slots[k], &guaranteed[k], base, slice);
424 if wider != base {
425 grew |= try_grow(k, wider, parts, delivered);
426 }
427 }
428 if !grew {
429 for &k in &order {
430 let free = free_pool(budget, parts);
431 let base = parts[k].1.as_ref().map_or(Share::default(), |p| p.cost);
432 let wider = widen(&slots[k], &guaranteed[k], base, free);
433 if wider != base && try_grow(k, wider, parts, delivered) {
434 grew = true;
435 break;
436 }
437 }
438 }
439 if !grew {
440 return;
441 }
442 }
443}