1use super::fabric_target::{Capability, Unplaceable};
21use super::metapipeline::Metapipeline;
22use super::plasticine::PlasticineTarget;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
26pub struct LinkBudget {
27 pub vector_links: usize,
28 pub scalar_links: usize,
29}
30
31impl LinkBudget {
32 pub fn spade_default() -> LinkBudget {
33 LinkBudget {
34 vector_links: 2,
35 scalar_links: 4,
36 }
37 }
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum Endpoint {
43 Cell(usize, usize),
45 Dram,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct MeshNet {
52 pub from: Endpoint,
53 pub to: Endpoint,
54}
55
56pub type Link = ((usize, usize), (usize, usize));
58
59pub struct MeshRouting {
64 pub links: Vec<Vec<Link>>,
65 pub occupancy: Vec<Vec<(Link, u8)>>,
66 pub legal: bool,
68 pub router: &'static str,
70}
71
72pub trait MeshRouter: Send + Sync {
75 fn route(&self, t: &PlasticineTarget, budget: LinkBudget, nets: &[MeshNet]) -> MeshRouting;
76}
77
78static MESH_ROUTER: std::sync::OnceLock<&'static dyn MeshRouter> = std::sync::OnceLock::new();
79
80pub fn install_mesh_router(router: &'static dyn MeshRouter) -> Result<(), &'static dyn MeshRouter> {
84 MESH_ROUTER.set(router)
85}
86
87pub fn mesh_router() -> Option<&'static dyn MeshRouter> {
89 MESH_ROUTER.get().copied()
90}
91
92#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
94pub struct StageAllocation {
95 pub stage: usize,
96 pub pcus: usize,
97 pub stages_in_last: usize,
99 pub cells: usize,
101}
102
103#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
105pub struct BufferAllocation {
106 pub edge: (usize, usize),
107 pub depth: usize,
109 pub words: usize,
110 pub pmus: usize,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
114pub struct Allocation {
115 pub stages: Vec<StageAllocation>,
116 pub buffers: Vec<BufferAllocation>,
117}
118
119impl Allocation {
120 pub fn pcus(&self) -> usize {
121 self.stages.iter().map(|s| s.pcus).sum()
122 }
123 pub fn pmus(&self) -> usize {
124 self.buffers.iter().map(|b| b.pmus).sum()
125 }
126}
127
128pub fn words_per_pmu(t: &PlasticineTarget) -> usize {
130 t.pmu_kib * 1024 / 4
131}
132
133pub fn pmus_for(words: usize, depth: usize, t: &PlasticineTarget) -> usize {
137 let per_buffer = (words_per_pmu(t) / depth.max(1)).max(1);
138 if words == 0 {
139 1
140 } else {
141 (words + per_buffer - 1) / per_buffer
142 }
143}
144
145pub fn allocate(
148 mp: &Metapipeline,
149 buffer_words: &[usize],
150 t: &PlasticineTarget,
151) -> Result<Allocation, Unplaceable> {
152 allocate_hosted(mp, buffer_words, t, &[])
153}
154
155pub fn allocate_hosted(
159 mp: &Metapipeline,
160 buffer_words: &[usize],
161 t: &PlasticineTarget,
162 ag_stages: &[usize],
163) -> Result<Allocation, Unplaceable> {
164 assert_eq!(buffer_words.len(), mp.edges.len(), "one tile size per edge");
165 mp.fits(t)?;
166 let per_pcu = t.pcu_stages.max(1);
167 let stages = mp
168 .stages
169 .iter()
170 .enumerate()
171 .map(|(i, s)| {
172 if ag_stages.contains(&i) {
173 return StageAllocation {
174 stage: i,
175 pcus: 0,
176 stages_in_last: 0,
177 cells: 0,
178 };
179 }
180 let slots = s.latency.max(s.cells).max(1);
183 let pcus = (slots + per_pcu - 1) / per_pcu;
184 StageAllocation {
185 stage: i,
186 pcus,
187 stages_in_last: slots - (pcus - 1) * per_pcu,
188 cells: s.cells,
189 }
190 })
191 .collect();
192 let mut buffers = Vec::new();
193 for (k, &(w, r)) in mp.edges.iter().enumerate() {
194 let depth = Metapipeline::buffer_depth(w, r);
195 let words = buffer_words[k];
196 buffers.push(BufferAllocation {
197 edge: (w, r),
198 depth,
199 words,
200 pmus: pmus_for(words, depth, t),
201 });
202 }
203 let alloc = Allocation { stages, buffers };
204 if alloc.pcus() > t.pcus() || alloc.pmus() > t.pmus() {
205 return Err(Unplaceable::TooSmall {
206 needed_rows: alloc.pcus(),
207 needed_cols: alloc.pmus(),
208 target_rows: t.pcus(),
209 target_cols: t.pmus(),
210 target: t.name,
211 });
212 }
213 Ok(alloc)
214}
215
216#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
218pub enum Unit {
219 Pcu { stage: usize, index: usize },
220 Pmu { edge: (usize, usize), index: usize },
221}
222
223#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
224pub struct Placed {
225 pub unit: Unit,
226 pub row: usize,
227 pub col: usize,
228}
229
230#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
232pub struct Route {
233 pub edge: (usize, usize),
234 pub hops: usize,
235 pub links: Vec<((usize, usize), (usize, usize))>,
237}
238
239#[derive(Clone, Debug, PartialEq, serde::Serialize)]
240pub struct Placement {
241 pub target: &'static str,
242 pub alloc: Allocation,
243 pub placed: Vec<Placed>,
244 pub routes: Vec<Route>,
245 pub pcu_total: usize,
246 pub pmu_total: usize,
247 pub budget: LinkBudget,
248 pub max_link_occupancy: usize,
250 pub routed_statically: bool,
252 pub router: &'static str,
255}
256
257impl Placement {
258 pub fn pcu_used(&self) -> usize {
259 self.alloc.pcus()
260 }
261 pub fn pmu_used(&self) -> usize {
262 self.alloc.pmus()
263 }
264 pub fn pcu_utilization(&self) -> f64 {
265 self.pcu_used() as f64 / self.pcu_total as f64
266 }
267 pub fn pmu_utilization(&self) -> f64 {
268 self.pmu_used() as f64 / self.pmu_total as f64
269 }
270 pub fn fu_utilization(&self, pcu_stages: usize) -> f64 {
272 let cells: usize = self.alloc.stages.iter().map(|s| s.cells).sum();
273 cells as f64 / (self.pcu_used() * pcu_stages.max(1)) as f64
274 }
275 pub fn total_hops(&self) -> usize {
276 self.routes.iter().map(|r| r.hops).sum()
277 }
278 pub fn max_hops(&self) -> usize {
279 self.routes.iter().map(|r| r.hops).max().unwrap_or(0)
280 }
281}
282
283fn is_pcu_cell(row: usize, col: usize) -> bool {
284 (row + col) % 2 == 0
285}
286
287fn manhattan(a: (usize, usize), b: (usize, usize)) -> usize {
288 a.0.abs_diff(b.0) + a.1.abs_diff(b.1)
289}
290
291fn xy_route(a: (usize, usize), b: (usize, usize)) -> Vec<((usize, usize), (usize, usize))> {
293 let mut links = Vec::new();
294 let (mut r, mut c) = a;
295 while c != b.1 {
296 let nc = if b.1 > c { c + 1 } else { c - 1 };
297 links.push(((r, c), (r, nc)));
298 c = nc;
299 }
300 while r != b.0 {
301 let nr = if b.0 > r { r + 1 } else { r - 1 };
302 links.push(((r, c), (nr, c)));
303 r = nr;
304 }
305 links
306}
307
308pub fn place(
312 alloc: &Allocation,
313 t: &PlasticineTarget,
314 budget: LinkBudget,
315) -> Result<Placement, Unplaceable> {
316 let (rows, cols) = (t.pcu_rows, t.pcu_cols);
317 let mut pcu_cells: Vec<(usize, usize)> = Vec::new();
318 let mut pmu_cells: Vec<(usize, usize)> = Vec::new();
319 for r in 0..rows {
320 let cs: Vec<usize> = if r % 2 == 0 {
321 (0..cols).collect()
322 } else {
323 (0..cols).rev().collect()
324 };
325 for c in cs {
326 if is_pcu_cell(r, c) {
327 pcu_cells.push((r, c))
328 } else {
329 pmu_cells.push((r, c))
330 }
331 }
332 }
333 if alloc.pcus() > pcu_cells.len() || alloc.pmus() > pmu_cells.len() {
334 return Err(Unplaceable::TooSmall {
335 needed_rows: alloc.pcus(),
336 needed_cols: alloc.pmus(),
337 target_rows: pcu_cells.len(),
338 target_cols: pmu_cells.len(),
339 target: t.name,
340 });
341 }
342 let mut placed = Vec::new();
343 let mut next_pcu = 0usize;
344 let mut stage_head: Vec<(usize, usize)> = Vec::new();
345 let mut stage_tail: Vec<(usize, usize)> = Vec::new();
346 let mut perimeter_hops: Vec<usize> = Vec::new();
348 for s in &alloc.stages {
349 if s.pcus == 0 {
350 let neighbour = pcu_cells[next_pcu.min(pcu_cells.len() - 1)];
353 let col = if neighbour.1 * 2 < cols { 0 } else { cols - 1 };
354 stage_head.push((neighbour.0, col));
355 stage_tail.push((neighbour.0, col));
356 perimeter_hops.push(1);
357 continue;
358 }
359 stage_head.push(pcu_cells[next_pcu]);
360 for i in 0..s.pcus {
361 let (r, c) = pcu_cells[next_pcu];
362 placed.push(Placed {
363 unit: Unit::Pcu {
364 stage: s.stage,
365 index: i,
366 },
367 row: r,
368 col: c,
369 });
370 next_pcu += 1;
371 }
372 stage_tail.push(pcu_cells[next_pcu - 1]);
373 perimeter_hops.push(0);
374 }
375 let mut free_pmu: Vec<(usize, usize)> = pmu_cells.clone();
376 let mut routes = Vec::new();
377 let mut link_use: std::collections::HashMap<((usize, usize), (usize, usize)), usize> =
378 std::collections::HashMap::new();
379 for b in &alloc.buffers {
380 let (w, r) = b.edge;
381 let tail = stage_tail[w];
382 let head = stage_head[r];
383 let mut links = Vec::new();
384 let mut hops = 0usize;
385 for i in 0..b.pmus {
386 let (idx, _) = free_pmu
388 .iter()
389 .enumerate()
390 .min_by_key(|(_, p)| manhattan(**p, tail) + manhattan(**p, head))
391 .expect("a free PMU cell");
392 let cell = free_pmu.remove(idx);
393 placed.push(Placed {
394 unit: Unit::Pmu {
395 edge: b.edge,
396 index: i,
397 },
398 row: cell.0,
399 col: cell.1,
400 });
401 let l1 = xy_route(tail, cell);
402 let l2 = xy_route(cell, head);
403 hops += l1.len() + l2.len() + perimeter_hops[w] + perimeter_hops[r];
404 links.extend(l1);
405 links.extend(l2);
406 }
407 for l in &links {
411 *link_use.entry(*l).or_insert(0) += 1;
412 }
413 routes.push(Route {
414 edge: b.edge,
415 hops,
416 links,
417 });
418 }
419 let max_link_occupancy = link_use.values().copied().max().unwrap_or(0);
420 Ok(Placement {
421 target: t.name,
422 alloc: alloc.clone(),
423 placed,
424 routes,
425 pcu_total: pcu_cells.len(),
426 pmu_total: pmu_cells.len(),
427 budget,
428 max_link_occupancy,
429 routed_statically: max_link_occupancy <= budget.vector_links,
430 router: "xy-static",
431 })
432}
433
434pub fn place_metapipeline(
436 mp: &Metapipeline,
437 buffer_words: &[usize],
438 t: &PlasticineTarget,
439 budget: LinkBudget,
440) -> Result<Placement, Unplaceable> {
441 let alloc = allocate(mp, buffer_words, t)?;
442 place(&alloc, t, budget)
443}
444
445fn replicate(one: &Allocation, n_stages: usize, par: usize) -> Allocation {
449 let mut stages = Vec::with_capacity(one.stages.len() * par);
450 let mut buffers = Vec::with_capacity(one.buffers.len() * par);
451 for k in 0..par {
452 let off = k * n_stages;
453 stages.extend(one.stages.iter().map(|s| StageAllocation {
454 stage: off + s.stage,
455 ..s.clone()
456 }));
457 buffers.extend(one.buffers.iter().map(|b| BufferAllocation {
458 edge: (off + b.edge.0, off + b.edge.1),
459 ..b.clone()
460 }));
461 }
462 Allocation { stages, buffers }
463}
464
465pub fn place_unrolled(
468 mp: &Metapipeline,
469 buffer_words: &[usize],
470 ag_stages: &[usize],
471 par: usize,
472 t: &PlasticineTarget,
473 budget: LinkBudget,
474) -> Result<Placement, Unplaceable> {
475 assert!(par >= 1, "an unroll factor is at least one");
476 let one = allocate_hosted(mp, buffer_words, t, ag_stages)?;
477 let alloc = replicate(&one, mp.stages.len(), par);
478 if alloc.pcus() > t.pcus() || alloc.pmus() > t.pmus() {
479 return Err(Unplaceable::TooSmall {
480 needed_rows: alloc.pcus(),
481 needed_cols: alloc.pmus(),
482 target_rows: t.pcus(),
483 target_cols: t.pmus(),
484 target: t.name,
485 });
486 }
487 match mesh_router() {
491 Some(router) => place_routed(&alloc, t, budget, router),
492 None => place(&alloc, t, budget),
493 }
494}
495
496pub fn dram_streams(
500 mp: &Metapipeline,
501 buffer_words: &[usize],
502 ag_stages: &[usize],
503 t: &PlasticineTarget,
504) -> Result<(usize, usize), Unplaceable> {
505 let one = allocate_hosted(mp, buffer_words, t, ag_stages)?;
506 let mut inbound = 0usize;
507 let mut outbound = 0usize;
508 for b in &one.buffers {
509 if ag_stages.contains(&b.edge.0) {
510 inbound += b.pmus;
511 }
512 if ag_stages.contains(&b.edge.1) {
513 outbound += b.pmus;
514 }
515 }
516 Ok((inbound, outbound))
517}
518
519pub fn max_par(
523 mp: &Metapipeline,
524 buffer_words: &[usize],
525 ag_stages: &[usize],
526 t: &PlasticineTarget,
527) -> Result<usize, Unplaceable> {
528 let one = allocate_hosted(mp, buffer_words, t, ag_stages)?;
529 let by_pcu = if one.pcus() == 0 {
530 usize::MAX
531 } else {
532 t.pcus() / one.pcus()
533 };
534 let by_pmu = if one.pmus() == 0 {
535 usize::MAX
536 } else {
537 t.pmus() / one.pmus()
538 };
539 let (inbound, outbound) = dram_streams(mp, buffer_words, ag_stages, t)?;
540 let ports = t.pcu_rows * 2 * LinkBudget::spade_default().vector_links;
541 let by_in = if inbound == 0 {
542 usize::MAX
543 } else {
544 ports / inbound
545 };
546 let by_out = if outbound == 0 {
547 usize::MAX
548 } else {
549 ports / outbound
550 };
551 Ok(by_pcu.min(by_pmu).min(by_in).min(by_out).max(1))
552}
553
554pub fn max_routable_par(
559 mp: &Metapipeline,
560 buffer_words: &[usize],
561 ag_stages: &[usize],
562 t: &PlasticineTarget,
563 budget: LinkBudget,
564) -> Result<(usize, usize), Unplaceable> {
565 let capacity = max_par(mp, buffer_words, ag_stages, t)?;
566 max_routable_par_within(mp, buffer_words, ag_stages, t, budget, capacity)
567}
568
569pub fn max_routable_par_within(
576 mp: &Metapipeline,
577 buffer_words: &[usize],
578 ag_stages: &[usize],
579 t: &PlasticineTarget,
580 budget: LinkBudget,
581 upper: usize,
582) -> Result<(usize, usize), Unplaceable> {
583 let capacity = max_par(mp, buffer_words, ag_stages, t)?;
584 let hi = upper.min(capacity).max(1);
585 let routes = |par: usize| -> Result<bool, Unplaceable> {
586 Ok(place_unrolled(mp, buffer_words, ag_stages, par, t, budget)?.routed_statically)
587 };
588 if routes(hi)? {
589 return Ok((hi, capacity));
590 }
591 let (mut lo, mut bad) = (1usize, hi);
593 while bad - lo > 1 {
594 let mid = lo + (bad - lo) / 2;
595 if routes(mid)? {
596 lo = mid
597 } else {
598 bad = mid
599 }
600 }
601 Ok((lo, capacity))
602}
603
604pub fn lanes_need_banks(t: &PlasticineTarget) -> Result<(), Unplaceable> {
607 let cap = Capability::Banks(t.pcu_lanes);
608 if t.provides(cap) {
609 Ok(())
610 } else {
611 Err(Unplaceable::MissingCapability {
612 needed: cap,
613 target: t.name,
614 })
615 }
616}
617
618fn balanced_pcu_cells(t: &PlasticineTarget) -> Vec<(usize, usize)> {
626 let (rows, cols) = (t.pcu_rows, t.pcu_cols);
627 let mut per_row: Vec<Vec<(usize, usize)>> = Vec::with_capacity(rows);
628 for r in 0..rows {
629 let cells: Vec<(usize, usize)> = (0..cols)
630 .filter(|&c| is_pcu_cell(r, c))
631 .map(|c| (r, c))
632 .collect();
633 let mut order = Vec::with_capacity(cells.len());
634 let (mut lo, mut hi) = (0usize, cells.len());
635 while lo < hi {
636 order.push(cells[lo]);
637 lo += 1;
638 if lo < hi {
639 hi -= 1;
640 order.push(cells[hi]);
641 }
642 }
643 per_row.push(order);
644 }
645 let width = per_row.iter().map(|v| v.len()).max().unwrap_or(0);
646 let mut out = Vec::with_capacity(rows * width);
647 for k in 0..width {
648 for row in &per_row {
649 if let Some(&c) = row.get(k) {
650 out.push(c);
651 }
652 }
653 }
654 out
655}
656
657pub fn place_routed(
665 alloc: &Allocation,
666 t: &PlasticineTarget,
667 budget: LinkBudget,
668 router: &dyn MeshRouter,
669) -> Result<Placement, Unplaceable> {
670 let (rows, cols) = (t.pcu_rows, t.pcu_cols);
671 let pcu_cells = balanced_pcu_cells(t);
672 let mut pmu_cells: Vec<(usize, usize)> = Vec::new();
673 for r in 0..rows {
674 for c in 0..cols {
675 if !is_pcu_cell(r, c) {
676 pmu_cells.push((r, c));
677 }
678 }
679 }
680 if alloc.pcus() > pcu_cells.len() || alloc.pmus() > pmu_cells.len() {
681 return Err(Unplaceable::TooSmall {
682 needed_rows: alloc.pcus(),
683 needed_cols: alloc.pmus(),
684 target_rows: pcu_cells.len(),
685 target_cols: pmu_cells.len(),
686 target: t.name,
687 });
688 }
689 let mut placed = Vec::new();
690 let mut next_pcu = 0usize;
691 let mut head: Vec<Endpoint> = Vec::new();
693 let mut tail: Vec<Endpoint> = Vec::new();
694 let mut chains: Vec<Vec<(usize, usize)>> = Vec::new();
695 for s in &alloc.stages {
696 if s.pcus == 0 {
697 head.push(Endpoint::Dram);
698 tail.push(Endpoint::Dram);
699 chains.push(Vec::new());
700 continue;
701 }
702 let cells: Vec<(usize, usize)> = (0..s.pcus).map(|i| pcu_cells[next_pcu + i]).collect();
703 for (i, &(r, c)) in cells.iter().enumerate() {
704 placed.push(Placed {
705 unit: Unit::Pcu {
706 stage: s.stage,
707 index: i,
708 },
709 row: r,
710 col: c,
711 });
712 }
713 next_pcu += s.pcus;
714 head.push(Endpoint::Cell(cells[0].0, cells[0].1));
715 let last = cells[cells.len() - 1];
716 tail.push(Endpoint::Cell(last.0, last.1));
717 chains.push(cells);
718 }
719 let mut nets: Vec<MeshNet> = Vec::new();
720 let mut net_owner: Vec<(usize, usize)> = Vec::new();
721 for (si, chain) in chains.iter().enumerate() {
722 for w in chain.windows(2) {
723 nets.push(MeshNet {
724 from: Endpoint::Cell(w[0].0, w[0].1),
725 to: Endpoint::Cell(w[1].0, w[1].1),
726 });
727 net_owner.push((alloc.stages[si].stage, alloc.stages[si].stage));
728 }
729 }
730 let side_pull = |p: (usize, usize)| p.1.min(cols - 1 - p.1);
731 let anchor = |e: Endpoint, p: (usize, usize)| -> usize {
732 match e {
733 Endpoint::Cell(rr, cc) => manhattan(p, (rr, cc)),
734 Endpoint::Dram => side_pull(p) + 1,
735 }
736 };
737 let mut free_pmu = pmu_cells.clone();
738 for b in &alloc.buffers {
739 let (w, r) = b.edge;
740 for i in 0..b.pmus {
741 let (idx, _) = free_pmu
742 .iter()
743 .enumerate()
744 .min_by_key(|(_, p)| anchor(tail[w], **p) + anchor(head[r], **p))
745 .expect("a free PMU cell");
746 let cell = free_pmu.remove(idx);
747 placed.push(Placed {
748 unit: Unit::Pmu {
749 edge: b.edge,
750 index: i,
751 },
752 row: cell.0,
753 col: cell.1,
754 });
755 nets.push(MeshNet {
756 from: tail[w],
757 to: Endpoint::Cell(cell.0, cell.1),
758 });
759 net_owner.push(b.edge);
760 nets.push(MeshNet {
761 from: Endpoint::Cell(cell.0, cell.1),
762 to: head[r],
763 });
764 net_owner.push(b.edge);
765 }
766 }
767 let routing = router.route(t, budget, &nets);
768 let mut routes: Vec<Route> = Vec::new();
769 let mut link_use: std::collections::HashMap<(Link, u8), usize> =
770 std::collections::HashMap::new();
771 for (i, (links, occupancy)) in routing.links.into_iter().zip(routing.occupancy).enumerate() {
772 for k in occupancy {
773 *link_use.entry(k).or_insert(0) += 1;
774 }
775 let edge = net_owner[i];
776 match routes.iter_mut().find(|rt| rt.edge == edge) {
777 Some(rt) => {
778 rt.hops += links.len();
779 rt.links.extend(links);
780 }
781 None => routes.push(Route {
782 edge,
783 hops: links.len(),
784 links,
785 }),
786 }
787 }
788 let max_link_occupancy = link_use.values().copied().max().unwrap_or(0);
789 Ok(Placement {
790 target: t.name,
791 alloc: alloc.clone(),
792 placed,
793 routes,
794 pcu_total: pcu_cells.len(),
795 pmu_total: pmu_cells.len(),
796 budget,
797 max_link_occupancy,
798 routed_statically: routing.legal,
799 router: routing.router,
800 })
801}