1use std::collections::{BTreeMap, BTreeSet};
33
34pub mod budget;
35mod engine;
36
37pub use engine::{floor_divider_latency, floor_divider_verilog};
38
39use super::{require_verified, Census, SpatialArtifact, SpatialBackend, SpatialLowerError};
40use crate::codegen_sva::fabric_grid::{emit_grid_verilog, route_loop_body};
41use crate::codegen_sva::fabric_target::Precision;
42use crate::codegen_sva::handshake_rtl::{emit_mcast_bus_verilog, emit_semaphore_verilog};
43use crate::codegen_sva::systolic::{emit_systolic_pipelined_loadable_verilog_starting, pipelined_latency, WeightStart};
44use crate::spatial::eval::{Inputs, Value};
45use crate::spatial::ir::*;
46use crate::spatial::passes::{LV_ITEM, LV_ITEM_X, LV_ITEM_Y};
47use crate::spatial::target::{precision_int_bits, SpatialTarget};
48use crate::spatial::value_range::{program_ranges, signed_bits, ProgramRanges};
49use crate::tensor_recognize::ElementwiseOp;
50
51#[derive(Clone, Debug, PartialEq)]
52pub struct VerilogArtifact {
53 pub top: String,
54 pub rtl: String,
55 pub testbench: String,
56 pub hex: Vec<(String, String)>,
57 pub census: Census,
58 pub cycle_model: u64,
59 pub cycle_floor: u64,
60}
61
62pub struct FabricBackend {
63 pub target: SpatialTarget,
64}
65
66impl FabricBackend {
67 pub fn new(target: SpatialTarget) -> Self {
68 FabricBackend { target }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
76enum Micro {
77 Guarded(Guard),
79 Fixed(Fixed),
81 Then(Fixed),
85 LoopStart {
86 var: LoopVar,
87 count: usize,
88 end: usize,
89 },
90 LoopEnd {
91 var: LoopVar,
92 count: usize,
93 start: usize,
94 },
95}
96
97#[derive(Clone, Debug, PartialEq, Eq)]
98enum Guard {
99 Reserve { buf: BufferId, pages: usize },
100 Wait { buf: BufferId, pages: usize },
101 SemWait { sem: SemId, until: SemCond },
102 Link { point: usize, ordinal: usize },
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
108enum Fixed {
109 Push {
110 buf: BufferId,
111 pages: usize,
112 },
113 Pop {
114 buf: BufferId,
115 pages: usize,
116 },
117 Barrier,
118 ReadTiles {
119 tensor: TensorId,
120 at: TileAddr,
121 block_h: usize,
122 block_w: usize,
123 stride_w: usize,
124 stride_h: usize,
125 into: BufferId,
126 section: Option<usize>,
128 },
129 WriteTiles {
130 from: BufferId,
131 tensor: TensorId,
132 at: TileAddr,
133 block_h: usize,
134 block_w: usize,
135 stride_w: usize,
136 stride_h: usize,
137 section: Option<usize>,
138 },
139 AcquireAcc {
140 tiles: usize,
141 cols: usize,
143 },
144 ReleaseAcc,
145 Matmul {
146 block: BlockShape,
147 per_site_n: usize,
148 sub_rows: usize,
150 operand: Precision,
151 ins: [BufferId; 2],
152 reload: Option<BufferId>,
153 tiles: usize,
154 stage: StageId,
156 array: (usize, usize),
159 },
160 Unary {
161 kind: UnaryKind,
162 ins: Vec<BufferId>,
163 tiles: usize,
164 stage: StageId,
165 },
166 Generic {
170 unit: Box<GenUnit>,
171 ins: Vec<BufferId>,
172 tiles: usize,
173 stage: StageId,
174 depth: u64,
177 },
178 PackAcc {
179 to: BufferId,
180 tiles: usize,
181 trunc32: bool,
182 accumulate: bool,
183 cols: usize,
185 },
186 SemSet {
187 sem: SemId,
188 value: SemValue,
189 },
190 SemInc {
191 sem: SemId,
192 at: Site,
193 by: usize,
194 },
195 McastSetValid {
196 sem: SemId,
197 group: GroupId,
198 },
199 McastSend {
200 buf: BufferId,
201 pages: usize,
202 group: GroupId,
203 },
204}
205
206#[derive(Clone, Debug, PartialEq, Eq)]
209struct GenUnit {
210 row: IterSpec,
211 col: IterSpec,
212 k: Option<IterSpec>,
213 maps: Vec<ElemAccess>,
214 body: TensorBody,
215 issue: Issue,
216 lanes: usize,
218 lane: LaneAxis,
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
224enum LaneAxis {
225 K,
226 Mid,
227 Out,
228}
229
230#[derive(Clone, Debug, PartialEq, Eq)]
238struct Issue {
239 ii: usize,
240 at: Vec<usize>,
241 body: usize,
242 shares: Vec<Share>,
244}
245
246#[derive(Clone, Debug, PartialEq, Eq)]
250struct Share {
251 then: usize,
252 otherwise: usize,
253 pred: Pred,
254}
255
256fn exclusive_sides(body: &TensorBody) -> Vec<(Pred, BTreeSet<usize>, BTreeSet<usize>)> {
259 fn uses(b: &TensorBody, out: &mut BTreeMap<usize, usize>) {
260 match b {
261 TensorBody::Operand(i) => *out.entry(*i).or_default() += 1,
262 TensorBody::Const(_) => {}
263 TensorBody::Add(x, y)
264 | TensorBody::Sub(x, y)
265 | TensorBody::Mul(x, y)
266 | TensorBody::Min(x, y)
267 | TensorBody::Max(x, y)
268 | TensorBody::FloorDiv(x, y) => {
269 uses(x, out);
270 uses(y, out);
271 }
272 TensorBody::Unary { arg, .. } | TensorBody::Shr { arg, .. } => uses(arg, out),
273 TensorBody::Select { cond, then, otherwise } => {
274 if let Some(i) = cond.operand() {
275 *out.entry(i).or_default() += 1;
276 }
277 uses(then, out);
278 uses(otherwise, out);
279 }
280 }
281 }
282 fn walk(b: &TensorBody, total: &BTreeMap<usize, usize>, out: &mut Vec<(Pred, BTreeSet<usize>, BTreeSet<usize>)>) {
283 match b {
284 TensorBody::Operand(_) | TensorBody::Const(_) => {}
285 TensorBody::Add(x, y)
286 | TensorBody::Sub(x, y)
287 | TensorBody::Mul(x, y)
288 | TensorBody::Min(x, y)
289 | TensorBody::Max(x, y)
290 | TensorBody::FloorDiv(x, y) => {
291 walk(x, total, out);
292 walk(y, total, out);
293 }
294 TensorBody::Unary { arg, .. } | TensorBody::Shr { arg, .. } => walk(arg, total, out),
295 TensorBody::Select { cond, then, otherwise } => {
296 if cond.operand().is_none() {
297 let only = |side: &TensorBody| -> BTreeSet<usize> {
298 let mut u = BTreeMap::new();
299 uses(side, &mut u);
300 u.into_iter().filter(|(i, n)| total.get(i) == Some(n)).map(|(i, _)| i).collect()
301 };
302 out.push((*cond, only(then), only(otherwise)));
303 }
304 walk(then, total, out);
305 walk(otherwise, total, out);
306 }
307 }
308 }
309 let mut total = BTreeMap::new();
310 uses(body, &mut total);
311 let mut out = Vec::new();
312 walk(body, &total, &mut out);
313 out
314}
315
316impl Issue {
317 fn of(
321 ports: &[usize],
322 order: &[usize],
323 index_of: &dyn Fn(usize) -> Option<usize>,
324 lat: usize,
325 sides: &[(Pred, BTreeSet<usize>, BTreeSet<usize>)],
326 ) -> Issue {
327 let n = ports.len();
328 let indexes: BTreeSet<usize> = (0..n).filter_map(index_of).collect();
329 let free = |s: usize| index_of(s).is_none() && !indexes.contains(&s);
330 let mut partner: Vec<Option<usize>> = vec![None; n];
331 let mut shares = Vec::new();
332 for (pred, then, otherwise) in sides {
333 for &t in then {
334 for &o in otherwise {
335 if t < n && o < n && ports[t] == ports[o] && free(t) && free(o) && partner[t].is_none() && partner[o].is_none() {
336 partner[t] = Some(o);
337 partner[o] = Some(t);
338 shares.push(Share { then: t, otherwise: o, pred: *pred });
339 }
340 }
341 }
342 }
343 let mut per_port: BTreeMap<usize, usize> = BTreeMap::new();
344 for s in 0..n {
345 if partner[s].is_none_or(|p| p > s) {
346 *per_port.entry(ports[s]).or_default() += 1;
347 }
348 }
349 let ii = per_port.values().copied().max().unwrap_or(1).max(1);
350 let mut at = vec![0usize; n];
351 let mut done = vec![false; n];
352 let mut used: BTreeMap<usize, BTreeSet<usize>> = BTreeMap::new();
353 for &s in order {
354 if let Some(p) = partner[s].filter(|p| done[*p]) {
355 at[s] = at[p];
356 done[s] = true;
357 continue;
358 }
359 let earliest = index_of(s).map_or(0, |ix| at[ix] + lat + 2);
362 let taken = used.entry(ports[s]).or_default();
363 let o = (earliest..earliest + ii)
366 .find(|o| !taken.contains(&(o % ii)))
367 .expect("a port takes no more reads than the issue interval has cycles");
368 taken.insert(o % ii);
369 at[s] = o;
370 done[s] = true;
371 }
372 let body = at.iter().map(|a| a + lat + 1).max().unwrap_or(1);
373 Issue { ii, at, body, shares }
374 }
375
376 fn cycles(&self, points: u64, depth: u64) -> u64 {
378 points.saturating_sub(1) * self.ii as u64 + self.body as u64 + depth + 1
379 }
380}
381
382impl GenUnit {
383 fn k_extent(&self) -> usize {
384 self.k.map_or(1, |k| k.extent)
385 }
386 fn points(&self) -> u64 {
388 (self.points_along(self.row.var) * self.points_along(self.col.var) * self.k.map_or(1, |k| self.points_along(k.var))) as u64
389 }
390 fn points_along(&self, v: LoopVar) -> usize {
392 let extent = if v == self.row.var {
393 self.row.extent
394 } else if v == self.col.var {
395 self.col.extent
396 } else {
397 self.k_extent()
398 };
399 if v == self.lane_var() {
400 extent.div_ceil(self.lanes)
401 } else {
402 extent
403 }
404 }
405 fn lane_var(&self) -> LoopVar {
407 lane_var(self.lane, &self.row, &self.col, self.k.as_ref())
408 }
409 fn lane_extent(&self) -> usize {
411 match self.lane {
412 LaneAxis::K => self.k_extent(),
413 LaneAxis::Mid if self.rows_inner() => self.row.extent,
414 LaneAxis::Mid => self.col.extent,
415 LaneAxis::Out if self.rows_inner() => self.col.extent,
416 LaneAxis::Out => self.row.extent,
417 }
418 }
419 fn tree(&self) -> bool {
422 self.lanes > 1 && (self.lane == LaneAxis::K || (self.lane == LaneAxis::Mid && self.lane_kind_reduces()))
423 }
424 fn tree_sums(&self) -> bool {
426 self.tree_pool() == ReducePool::Sum
427 }
428 fn tree_pool(&self) -> ReducePool {
430 let v = self.lane_var();
431 let kind = if self.lane == LaneAxis::K { None } else if v == self.row.var { Some(self.row.kind) } else { Some(self.col.kind) };
432 match kind {
433 Some(IterKind::Reduce { pool }) => pool,
434 _ => ReducePool::Sum,
435 }
436 }
437 fn lane_kind_reduces(&self) -> bool {
438 let v = self.lane_var();
439 let kind = if v == self.row.var { self.row.kind } else if v == self.col.var { self.col.kind } else { IterKind::Reduce { pool: ReducePool::Sum } };
440 matches!(kind, IterKind::Reduce { .. })
441 }
442 fn tree_levels(&self) -> u64 {
445 if self.tree() {
446 self.lanes.trailing_zeros() as u64
447 } else {
448 0
449 }
450 }
451 fn broadcast(&self, s: usize) -> bool {
454 if self.gathers(s) {
455 return false;
456 }
457 let v = self.lane_var();
458 self.maps[s].terms.iter().filter(|(t, _)| *t == v).map(|(_, st)| *st).sum::<i64>() == 0
459 }
460 fn slots(&self) -> usize {
461 self.maps.len()
462 }
463 fn rows_inner(&self) -> bool {
466 matches!(self.row.kind, IterKind::Reduce { .. }) && matches!(self.col.kind, IterKind::Parallel)
467 }
468 fn gathers(&self, s: usize) -> bool {
471 gathers(&self.maps, s)
472 }
473}
474
475fn gathers(maps: &[ElemAccess], s: usize) -> bool {
477 index_slot(maps, s).is_some()
478}
479
480fn index_slot(maps: &[ElemAccess], s: usize) -> Option<usize> {
483 maps[s].indirect.filter(|ind| !(ind.operand == s && ind.stride == 0)).map(|ind| ind.operand)
484}
485
486fn read_order(maps: &[ElemAccess]) -> Result<Vec<usize>, SpatialLowerError> {
489 let index_of = |s: usize| index_slot(maps, s);
490 for s in 0..maps.len() {
491 if let Some(ix) = index_of(s) {
492 if ix == s || ix >= maps.len() || index_of(ix).is_some() {
493 return Err(SpatialLowerError::Unsupported(
494 "a gather whose index slot is itself gathered, or names itself or no slot",
495 ));
496 }
497 }
498 }
499 let mut order: Vec<usize> = (0..maps.len()).filter(|s| index_of(*s).is_none()).collect();
500 order.extend((0..maps.len()).filter(|s| index_of(*s).is_some()));
501 Ok(order)
502}
503
504fn lane_var(lane: LaneAxis, row: &IterSpec, col: &IterSpec, k: Option<&IterSpec>) -> LoopVar {
507 let rows_inner = matches!(row.kind, IterKind::Reduce { .. }) && matches!(col.kind, IterKind::Parallel);
508 match lane {
509 LaneAxis::K => k.map_or(col.var, |k| k.var),
510 LaneAxis::Mid if rows_inner => row.var,
511 LaneAxis::Mid => col.var,
512 LaneAxis::Out if rows_inner => col.var,
513 LaneAxis::Out => row.var,
514 }
515}
516
517#[allow(clippy::too_many_arguments)]
538fn lanes_of(
539 widest: usize,
540 row: &IterSpec,
541 col: &IterSpec,
542 k: Option<&IterSpec>,
543 maps: &[ElemAccess],
544 body: &TensorBody,
545 ports: &[usize],
546 order: &[usize],
547 lat: usize,
548) -> (usize, LaneAxis, Issue) {
549 let index_of = |s: usize| index_slot(maps, s);
550 let sides = exclusive_sides(body);
551 let scalar = Issue::of(ports, order, &index_of, lat, &sides);
552 let rows_inner = matches!(row.kind, IterKind::Reduce { .. }) && matches!(col.kind, IterKind::Parallel);
553 let extent = |v: LoopVar| -> usize {
554 if v == row.var {
555 row.extent
556 } else if v == col.var {
557 col.extent
558 } else {
559 k.map_or(1, |k| k.extent)
560 }
561 };
562 let kind = |v: LoopVar| -> IterKind {
563 if v == row.var {
564 row.kind
565 } else if v == col.var {
566 col.kind
567 } else {
568 IterKind::Reduce { pool: ReducePool::Sum }
569 }
570 };
571 let stride = |m: &ElemAccess, v: LoopVar| -> i64 { m.terms.iter().filter(|(t, _)| *t == v).map(|(_, s)| *s).sum() };
572 let axes: Vec<LoopVar> = [Some(row.var), Some(col.var), k.map(|k| k.var)].into_iter().flatten().collect();
573 let points = |v: LoopVar, l: usize| -> u64 { axes.iter().map(|a| if *a == v { extent(*a).div_ceil(l) } else { extent(*a) } as u64).product() };
574 let mut choices = vec![(points(col.var, 1) * scalar.ii as u64, 1usize, LaneAxis::Mid, scalar)];
576 let scalar_only = body.post_order().iter().any(|n| matches!(n, TensorBody::FloorDiv(..)));
577 for lane in [LaneAxis::K, LaneAxis::Mid, LaneAxis::Out] {
578 if scalar_only || (lane == LaneAxis::K && k.is_none()) || (lane == LaneAxis::Out && !rows_inner) {
581 continue;
582 }
583 let v = lane_var(lane, row, col, k);
584 if extent(v) < 2 {
585 continue;
586 }
587 let levels = |l: usize| if matches!(kind(v), IterKind::Reduce { .. }) { l.trailing_zeros() as u64 } else { 0 };
588 let mut l = 2;
589 while l <= widest {
590 let aligned = maps.iter().enumerate().all(|(s, m)| match (index_of(s).is_some(), stride(m, v)) {
591 (true, moves) => moves == 0,
592 (false, 0) => true,
593 (false, 1) => m.base.rem_euclid(l as i64) == 0 && axes.iter().filter(|a| **a != v).all(|a| stride(m, *a).rem_euclid(l as i64) == 0),
594 _ => false,
595 });
596 if !aligned {
597 break;
598 }
599 let kept: Vec<(Pred, BTreeSet<usize>, BTreeSet<usize>)> = sides
600 .iter()
601 .filter(|(pred, _, _)| pred.var() != v || matches!(pred.rhs(), PredRhs::Const(b) if b % l == 0))
602 .cloned()
603 .collect();
604 let issue = Issue::of(ports, order, &index_of, lat, &kept);
605 choices.push((points(v, l) * issue.ii as u64 + levels(l), l, lane, issue));
606 l *= 2;
607 }
608 }
609 let (_, lanes, lane, issue) = choices.into_iter().min_by_key(|(cycles, lanes, lane, _)| (*cycles, *lanes, *lane)).expect("the scalar choice");
610 (lanes, lane, issue)
611}
612
613#[derive(Clone, Debug, PartialEq, Eq)]
614enum UnaryKind {
615 Requant { m: i64, s: u32 },
616 Bias,
617 Relu,
618 Scalar(ScalarBody),
619 Elementwise(ElementwiseOp),
620}
621
622struct EnginePlan {
624 site: Site,
625 engine: Engine,
626 class: usize,
627 micro: Vec<Micro>,
628 item_start: usize,
629 item_count: usize,
630}
631
632struct Ctx<'a> {
633 p: &'a SpatialProgram,
634 te: usize,
635 tr: usize,
636 tc: usize,
637 grid_nx: usize,
638 plan_acc: AccPolicy,
639 lat: u64,
642 ranges: ProgramRanges,
644 banks: usize,
646 lanes: usize,
648 extent: Vec<usize>,
650 phase_flags: std::cell::RefCell<BTreeSet<u64>>,
652 halts: Vec<(SemId, usize)>,
655 multiplier: Option<(u32, u32)>,
657 pitch: Vec<usize>,
659 page_pitch: BTreeMap<BufferId, (usize, usize)>,
661}
662
663impl Ctx<'_> {
664 fn ph_ge(&self, k: u64) -> String {
667 if k == 0 {
668 return "1'b1".into();
669 }
670 self.phase_flags.borrow_mut().insert(k);
671 format!("ph_ge{k}")
672 }
673 fn ph_lt(&self, k: u64) -> String {
675 if k == 0 {
676 return "1'b0".into();
677 }
678 format!("!{}", self.ph_ge(k))
679 }
680 fn acc_of(&self, stage: StageId) -> AccPolicy {
683 self.p
684 .sections
685 .iter()
686 .find(|s| (s.stages.0..s.stages.1).contains(&stage.idx()))
687 .and_then(|s| s.plan)
688 .map(|pl| pl.acc)
689 .unwrap_or(self.plan_acc)
690 }
691 fn page_elems(&self, b: BufferId) -> usize {
692 self.extent[b.idx()] * self.te
693 }
694 fn tensor_words(&self, t: &TensorDecl) -> usize {
697 tensor_words(t, self.pitch[t.id.idx()], self.te)
698 }
699 fn page_words(&self, b: BufferId) -> usize {
702 match self.page_pitch.get(&self.template(b)) {
703 Some((w, pitch)) => self.page_elems(b) / w * pitch,
704 None => self.page_elems(b),
705 }
706 }
707 fn page_row(&self, b: BufferId, width: usize) -> usize {
710 self.page_pitch.get(&self.template(b)).map_or(width, |(_, pitch)| *pitch)
711 }
712 fn walks_runs(&self, f: &Fixed) -> bool {
715 self.run(f) > 1 || matches!(f, Fixed::ReadTiles { into, .. } if self.page_pitch.contains_key(&self.template(*into)))
716 }
717 fn depth(&self, b: BufferId) -> usize {
718 match self.p.buffer(b).kind {
719 BufferKind::Fifo { depth_pages, .. } | BufferKind::Interm { depth_pages, .. } => {
720 depth_pages
721 }
722 BufferKind::Accumulator { .. } => 1,
723 }
724 }
725 fn buffer_width(&self, b: BufferId) -> u32 {
727 let t = self.template(b);
728 self.p
729 .buffers
730 .iter()
731 .filter(|x| self.template(x.id) == t)
732 .filter_map(|x| self.ranges.buffers[x.id.idx()])
733 .map(signed_bits)
734 .max()
735 .unwrap_or(1)
736 }
737 fn template(&self, b: BufferId) -> BufferId {
738 self.p.buffer(b).origin.unwrap_or(b)
739 }
740 fn duration(&self, f: &Fixed) -> u64 {
743 let te = self.te as u64;
744 let lat = self.lat;
745 match f {
746 Fixed::Push { .. }
747 | Fixed::Pop { .. }
748 | Fixed::Barrier
749 | Fixed::ReleaseAcc
750 | Fixed::SemSet { .. }
751 | Fixed::SemInc { .. }
752 | Fixed::McastSetValid { .. } => 1,
753 Fixed::ReadTiles {
754 block_h, block_w, at, ..
755 } => self.walk_cycles(f, *block_h, *block_w) + self.gather_skew(at) + lat,
756 Fixed::WriteTiles {
757 block_h, block_w, ..
758 } => self.walk_cycles(f, *block_h, *block_w) + lat,
759 Fixed::AcquireAcc { tiles, cols } => self.acc_walk_cycles(*tiles, *cols, self.acc_run(f)),
760 Fixed::PackAcc { tiles, accumulate, cols, .. } => self.acc_walk_cycles(*tiles, *cols, self.acc_run(f)) + if *accumulate { lat } else { 0 },
761 Fixed::Unary { tiles, .. } => *tiles as u64 * te + lat,
762 Fixed::Generic { unit, depth, .. } => unit.issue.cycles(unit.points(), *depth),
763 Fixed::Matmul { block, per_site_n, sub_rows, ins, reload, array, .. } => {
764 let (osh, ibw) = (block.osh as u64, block.ibw as u64);
765 let k = ibw * self.tc as u64;
766 let l = pipelined_latency(array.0, array.1) as u64;
767 let run = self.matmul_run(block, self.page_row(ins[1], *per_site_n), *per_site_n, *sub_rows);
768 let (reload, a, b) = self.matmul_loads(block, reload.is_some(), run);
769 if self.matvec(block) {
770 reload + a + b + lat + 2
773 } else {
774 reload + a + b + k + osh * self.tr as u64 + l + 1 + lat
775 }
776 }
777 Fixed::McastSend { buf, pages, .. } => (*pages * self.page_elems(*buf)) as u64 + 1 + lat,
778 }
779 }
780 fn runs(&self) -> bool {
782 self.te == 1 && self.banks > 1
783 }
784 fn align(&self, x: usize) -> usize {
793 if x == 0 {
794 self.banks
795 } else {
796 (1usize << x.trailing_zeros()).min(self.banks)
797 }
798 }
799 fn rows_align(&self, rows: usize, width: usize) -> usize {
801 if rows <= 1 {
802 self.banks
803 } else {
804 self.align(width)
805 }
806 }
807 fn tensor_align(&self, at: &TileAddr, block_h: usize, stride_h: usize, section: Option<usize>) -> usize {
811 let coords: Vec<(usize, usize)> = self
812 .p
813 .work
814 .items
815 .iter()
816 .filter(|it| section.is_none() || self.p.section_of_item(it.id) == section)
817 .map(|it| it.coord)
818 .collect();
819 let mut w = self.align(at.base).min(self.rows_align(block_h, stride_h));
820 if let Some(ind) = at.indirect {
821 w = w.min(self.align(ind.tile_stride));
822 }
823 for (v, stride) in &at.steps {
824 if *v == LV_ITEM_Y {
825 w = coords.iter().fold(w, |w, c| w.min(self.align(c.0 * stride)));
826 } else if *v == LV_ITEM_X {
827 w = coords.iter().fold(w, |w, c| w.min(self.align(c.1 * stride)));
828 } else {
829 w = w.min(self.align(*stride));
830 }
831 }
832 w
833 }
834 fn run(&self, f: &Fixed) -> usize {
838 match f {
839 Fixed::ReadTiles { at, block_h, block_w, stride_w, stride_h, section, into, .. } if self.runs() && *stride_w == 1 => {
840 self.rows_align(*block_h, self.page_row(*into, *block_w)).min(self.tensor_align(at, *block_h, *stride_h, *section))
841 }
842 Fixed::WriteTiles { at, block_h, block_w, stride_w, stride_h, section, .. } if self.runs() && *stride_w == 1 => {
843 self.rows_align(*block_h, *block_w).min(self.tensor_align(at, *block_h, *stride_h, *section))
844 }
845 _ => 1,
846 }
847 }
848 fn acc_run(&self, f: &Fixed) -> usize {
852 match f {
853 Fixed::AcquireAcc { .. } if self.runs() => self.banks,
854 Fixed::PackAcc { tiles, cols, .. } if self.runs() => self.rows_align(tiles.div_ceil((*cols).max(1)), *cols),
855 _ => 1,
856 }
857 }
858 fn matmul_run(&self, block: &BlockShape, b_row: usize, per_site_n: usize, sub_rows: usize) -> usize {
863 if !self.runs() {
864 return 1;
865 }
866 let (osh, osw, ibw) = (block.osh, block.osw, block.ibw);
867 let mut w = self.rows_align(osh, osw).min(self.rows_align(osh, ibw)).min(self.rows_align(ibw, b_row));
868 if sub_rows > 1 {
869 w = w.min(self.align(osh * ibw));
870 }
871 if per_site_n != osw {
872 w = w.min(self.align(osw));
873 }
874 w
875 }
876 fn matvec(&self, block: &BlockShape) -> bool {
880 block.osh * self.tr == 1
881 }
882 fn matmul_loads(&self, block: &BlockShape, reload: bool, run: usize) -> (u64, u64, u64) {
885 let (osh, osw, ibw, te) = (block.osh, block.osw, block.ibw, self.te);
886 let rows = |n: usize, width: usize| -> u64 { (n * (width * te).div_ceil(run)) as u64 };
887 (if reload { rows(osh, osw) } else { 0 }, rows(osh, ibw), rows(ibw, osw))
888 }
889 fn acc_walk_cycles(&self, tiles: usize, cols: usize, run: usize) -> u64 {
892 let elems = tiles * self.te;
893 match run {
894 1 => elems as u64,
895 run => {
896 let cols = cols.max(1);
897 ((elems / cols) * cols.div_ceil(run) + (elems % cols).div_ceil(run)) as u64
898 }
899 }
900 }
901 fn walk_cycles(&self, f: &Fixed, block_h: usize, block_w: usize) -> u64 {
903 match self.run(f) {
904 1 => (block_h * block_w) as u64 * self.te as u64,
905 run => (block_h * block_w.div_ceil(run)) as u64,
906 }
907 }
908 fn gather_skew(&self, at: &TileAddr) -> u64 {
910 if at.indirect.is_some() {
911 self.lat
912 } else {
913 0
914 }
915 }
916}
917
918fn walk_addr(ctx: &Ctx, tensor: TensorId, at: &TileAddr, strides: (usize, usize), loops: &[(LoopVar, usize)]) -> (TileAddr, usize, usize) {
923 let (cols, pitch) = (ctx.p.tensor(tensor).tiles.cols_t, ctx.pitch[tensor.idx()]);
924 let mut at = at.clone();
925 if ctx.runs() {
926 at.steps.retain(|(v, _)| *v == LV_ITEM_Y || *v == LV_ITEM_X || loops.iter().any(|(l, count)| l == v && *count > 1));
927 }
928 (pitched_addr(&at, cols, pitch), pitched(strides.0, cols, pitch), pitched(strides.1, cols, pitch))
929}
930
931fn lower_ops(ctx: &Ctx, ops: &[TaggedOp], out: &mut Vec<Micro>) -> Result<(), SpatialLowerError> {
932 lower_ops_within(ctx, ops, &mut Vec::new(), out)
933}
934
935fn lower_ops_within(ctx: &Ctx, ops: &[TaggedOp], loops: &mut Vec<(LoopVar, usize)>, out: &mut Vec<Micro>) -> Result<(), SpatialLowerError> {
937 for t in ops {
938 match &t.op {
939 ChoreoOp::Reserve { buf, pages } => out.push(Micro::Guarded(Guard::Reserve { buf: *buf, pages: *pages })),
940 ChoreoOp::Push { buf, pages } => out.push(Micro::Fixed(Fixed::Push { buf: *buf, pages: *pages })),
941 ChoreoOp::Wait { buf, pages } => out.push(Micro::Guarded(Guard::Wait { buf: *buf, pages: *pages })),
942 ChoreoOp::Pop { buf, pages } => out.push(Micro::Fixed(Fixed::Pop { buf: *buf, pages: *pages })),
943 ChoreoOp::ReadTiles { tensor, at, block_h, block_w, stride_w, stride_h, into } => {
944 let (at, stride_w, stride_h) = walk_addr(ctx, *tensor, at, (*stride_w, *stride_h), loops);
945 out.push(Micro::Fixed(Fixed::ReadTiles { tensor: *tensor, at, block_h: *block_h, block_w: *block_w, stride_w, stride_h, into: *into, section: ctx.p.section_of_stage(t.stage) }))
946 }
947 ChoreoOp::ReadBarrier | ChoreoOp::WriteBarrier | ChoreoOp::Flush { .. } => out.push(Micro::Fixed(Fixed::Barrier)),
948 ChoreoOp::WriteTiles { from, tensor, at, block_h, block_w, stride_w, stride_h } => {
949 let (at, stride_w, stride_h) = walk_addr(ctx, *tensor, at, (*stride_w, *stride_h), loops);
950 out.push(Micro::Fixed(Fixed::WriteTiles { from: *from, tensor: *tensor, at, block_h: *block_h, block_w: *block_w, stride_w, stride_h, section: ctx.p.section_of_stage(t.stage) }))
951 }
952 ChoreoOp::AcquireAcc { tiles } => out.push(Micro::Fixed(Fixed::AcquireAcc { tiles: *tiles, cols: 0 })),
953 ChoreoOp::ReleaseAcc => out.push(Micro::Fixed(Fixed::ReleaseAcc)),
954 ChoreoOp::Compute { op, ins, acc_reload } => {
955 let region = out.iter().rev().find_map(|m| match m {
957 Micro::Fixed(Fixed::AcquireAcc { tiles, .. }) => Some(Some(*tiles)),
958 Micro::Fixed(Fixed::ReleaseAcc) => Some(None),
959 _ => None,
960 });
961 let tiles = region.flatten().unwrap_or(ctx.p.acc_tiles_used).max(1);
962 match op {
963 ComputeOp::MatmulTiles { block, sub_blocks, operand, .. } => {
964 if ins.len() != 2 {
965 return Err(SpatialLowerError::Unsupported("a tile matmul with two operand buffers"));
966 }
967 if precision_int_bits(*operand).is_none() {
968 return Err(SpatialLowerError::Precision { needed: *operand });
969 }
970 out.push(Micro::Fixed(Fixed::Matmul { block: *block, per_site_n: sub_blocks.1 * block.osw, sub_rows: sub_blocks.0, operand: *operand, ins: [ins[0], ins[1]], reload: *acc_reload, tiles: block.osh * block.osw, stage: t.stage, array: (block.ibw * ctx.tc, block.osw * ctx.tc) }));
971 }
972 ComputeOp::Requant { m, s } => out.push(Micro::Fixed(Fixed::Unary { kind: UnaryKind::Requant { m: *m, s: *s }, ins: ins.clone(), tiles, stage: t.stage })),
973 ComputeOp::Bias { .. } => out.push(Micro::Fixed(Fixed::Unary { kind: UnaryKind::Bias, ins: ins.clone(), tiles, stage: t.stage })),
974 ComputeOp::Relu => out.push(Micro::Fixed(Fixed::Unary { kind: UnaryKind::Relu, ins: ins.clone(), tiles, stage: t.stage })),
975 ComputeOp::Scalar(body) => out.push(Micro::Fixed(Fixed::Unary { kind: UnaryKind::Scalar(body.clone()), ins: ins.clone(), tiles, stage: t.stage })),
976 ComputeOp::Generic { iters, ins: maps, out: omap, body: TensorBody::Add(a, b) }
981 if ctx.p.generic_is_plain_pointwise(iters, maps, omap, ins) && matches!(**a, TensorBody::Operand(0)) && matches!(**b, TensorBody::Operand(1)) =>
982 {
983 out.push(Micro::Fixed(Fixed::Unary { kind: UnaryKind::Elementwise(ElementwiseOp::Add), ins: ins.clone(), tiles, stage: t.stage }))
984 }
985 ComputeOp::Generic { iters, ins: maps, out: omap, body: TensorBody::Mul(a, b) }
986 if ctx.p.generic_is_plain_pointwise(iters, maps, omap, ins) && matches!(**a, TensorBody::Operand(0)) && matches!(**b, TensorBody::Operand(1)) =>
987 {
988 out.push(Micro::Fixed(Fixed::Unary { kind: UnaryKind::Elementwise(ElementwiseOp::Mul), ins: ins.clone(), tiles, stage: t.stage }))
989 }
990 ComputeOp::Generic { iters, ins: maps, body, .. } => {
996 let (row, col, k) = match iters.as_slice() {
997 [r, c] => (*r, *c, None),
998 [r, c, k] => (*r, *c, Some(*k)),
999 _ => return Err(SpatialLowerError::Unsupported("a Generic whose space is not (rows, cols) or (rows, cols, contraction)")),
1000 };
1001 if k.is_some_and(|k| !matches!(k.kind, IterKind::Reduce { pool: ReducePool::Sum })) {
1002 return Err(SpatialLowerError::Unsupported("a Generic whose contraction axis reduces by a pool other than a sum"));
1003 }
1004 if !body.integral() || ins.iter().any(|b| precision_int_bits(ctx.p.buffer(*b).dtype).is_none()) {
1005 return Err(SpatialLowerError::Unsupported("a float row op on the integer fabric"));
1006 }
1007 if maps.len() != ins.len() || ctx.tr != ctx.tc {
1008 return Err(SpatialLowerError::Unsupported("a Generic whose maps do not match its operands, or a non-square tile"));
1009 }
1010 let tiles = if matches!(col.kind, IterKind::Reduce { .. }) { 1 } else { col.extent.div_ceil(ctx.tr) };
1011 let order = read_order(maps)?;
1012 let ports: Vec<usize> = ins.iter().map(|b| ctx.template(*b).idx()).collect();
1013 let (lanes, lane, issue) = lanes_of(ctx.lanes, &row, &col, k.as_ref(), maps, body, &ports, &order, ctx.lat as usize);
1014 out.push(Micro::Fixed(Fixed::Generic {
1015 unit: Box::new(GenUnit { row, col, k, maps: maps.clone(), body: body.clone(), issue, lanes, lane }),
1016 ins: ins.clone(),
1017 tiles,
1018 stage: t.stage,
1019 depth: 0,
1020 }))
1021 }
1022 }
1023 }
1024 ChoreoOp::PackAcc { to, tiles, accumulate, .. } => out.push(Micro::Fixed(Fixed::PackAcc { to: *to, tiles: *tiles, trunc32: ctx.acc_of(t.stage) == AccPolicy::I32, accumulate: *accumulate, cols: 0 })),
1025 ChoreoOp::SemSet { sem, value } => out.push(Micro::Fixed(Fixed::SemSet { sem: *sem, value: *value })),
1026 ChoreoOp::SemInc { sem, at, by } => out.push(Micro::Fixed(Fixed::SemInc { sem: *sem, at: *at, by: *by })),
1027 ChoreoOp::SemWait { sem, until } => {
1028 out.push(Micro::Guarded(Guard::SemWait { sem: *sem, until: *until }));
1029 if let Some(ordinal) = ctx.halts.iter().position(|(g, _)| g == sem) {
1030 out.push(Micro::Guarded(Guard::Link { point: ctx.halts[ordinal].1, ordinal }));
1031 }
1032 }
1033 ChoreoOp::McastSetValid { sem, group } => out.push(Micro::Fixed(Fixed::McastSetValid { sem: *sem, group: *group })),
1034 ChoreoOp::McastSend { buf, pages, group, .. } => out.push(Micro::Fixed(Fixed::McastSend { buf: *buf, pages: *pages, group: *group })),
1035 ChoreoOp::Barrier { .. } => return Err(SpatialLowerError::Unsupported("a group barrier on the fabric")),
1036 ChoreoOp::Loop { count: LoopCount::Trip { .. }, .. } => {
1037 return Err(SpatialLowerError::Unsupported("a loop whose count the program reads from its data (a trip) — the fabric's loop micro-ops count to a constant"))
1038 }
1039 ChoreoOp::Loop { var, count, body } => {
1040 let start = out.len();
1041 out.push(Micro::LoopStart { var: *var, count: count.bound(), end: 0 });
1042 loops.push((*var, count.bound()));
1043 lower_ops_within(ctx, body, loops, out)?;
1044 loops.pop();
1045 let end = out.len();
1046 out.push(Micro::LoopEnd { var: *var, count: count.bound(), start });
1047 if let Micro::LoopStart { end: e, .. } = &mut out[start] {
1048 *e = end;
1049 }
1050 }
1051 }
1052 }
1053 Ok(())
1054}
1055
1056fn effect_signals(ctx: &Ctx, f: &Fixed) -> Option<Vec<String>> {
1059 Some(match f {
1060 Fixed::Push { buf, .. } => vec![format!("f{}_push", ctx.template(*buf).idx())],
1061 Fixed::Pop { buf, .. } => vec![format!("f{}_pop", ctx.template(*buf).idx())],
1062 Fixed::SemInc { sem, .. } => vec![format!("s{}_inc", sem.idx())],
1063 Fixed::SemSet { sem, .. } => vec![format!("s{}_set", sem.idx())],
1064 Fixed::McastSetValid { sem, group } => vec![format!("mv{}_g{}", sem.idx(), group.idx())],
1065 Fixed::Barrier | Fixed::ReleaseAcc => Vec::new(),
1066 _ => return None,
1067 })
1068}
1069
1070fn fold_effects(ctx: &Ctx, micro: &mut [Micro]) {
1076 let mut used: Option<BTreeSet<String>> = None;
1077 for m in micro.iter_mut() {
1078 let Micro::Fixed(f) = m else {
1079 used = None;
1080 continue;
1081 };
1082 let f = f.clone();
1083 let signals = effect_signals(ctx, &f);
1084 if let (Some(sig), Some(u)) = (&signals, used.as_mut()) {
1085 if sig.iter().all(|s| !u.contains(s)) {
1086 u.extend(sig.iter().cloned());
1087 *m = Micro::Then(f);
1088 continue;
1089 }
1090 }
1091 used = Some(signals.unwrap_or_default().into_iter().collect());
1092 }
1093}
1094
1095#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1097enum Resource {
1098 Fifo(BufferId),
1099 Sem(SemId),
1100 Link,
1102}
1103
1104fn effect_resource(f: &Fixed) -> Option<Resource> {
1105 match f {
1106 Fixed::Push { buf, .. } | Fixed::Pop { buf, .. } => Some(Resource::Fifo(*buf)),
1107 Fixed::SemInc { sem, .. } | Fixed::SemSet { sem, .. } | Fixed::McastSetValid { sem, .. } => Some(Resource::Sem(*sem)),
1108 _ => None,
1109 }
1110}
1111
1112fn guard_resource(g: &Guard) -> Resource {
1113 match g {
1114 Guard::Reserve { buf, .. } | Guard::Wait { buf, .. } => Resource::Fifo(*buf),
1115 Guard::SemWait { sem, .. } => Resource::Sem(*sem),
1116 Guard::Link { .. } => Resource::Link,
1117 }
1118}
1119
1120#[derive(Clone, Copy, Default)]
1123struct OwnDelta {
1124 count: i64,
1125 reserved: i64,
1126 sem: i64,
1127 set: bool,
1128}
1129
1130fn own_deltas(micro: &[Micro], pc: usize, site: Site) -> BTreeMap<Resource, OwnDelta> {
1132 let mut out: BTreeMap<Resource, OwnDelta> = BTreeMap::new();
1133 for m in µ[pc..after_effects(micro, pc)] {
1134 let (Micro::Fixed(f) | Micro::Then(f)) = m else { continue };
1135 let Some(r) = effect_resource(f) else { continue };
1136 let d = out.entry(r).or_default();
1137 match f {
1138 Fixed::Push { pages, .. } => {
1139 d.count += *pages as i64;
1140 d.reserved -= *pages as i64;
1141 }
1142 Fixed::Pop { pages, .. } => d.count -= *pages as i64,
1143 Fixed::SemInc { at, by, .. } if *at == site => d.sem += *by as i64,
1144 Fixed::SemInc { .. } => {}
1145 _ => d.set = true,
1146 }
1147 }
1148 out
1149}
1150
1151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1157enum Early {
1158 Count(BufferId, i64),
1160 Free(BufferId, i64),
1162 AtLeast(SemId, i64),
1164 Equals(SemId, i64),
1166}
1167
1168fn early_check(g: &Guard, own: &BTreeMap<Resource, OwnDelta>) -> Option<Early> {
1169 let d = own.get(&guard_resource(g)).copied().unwrap_or_default();
1170 match g {
1171 Guard::Wait { buf, pages } => Some(Early::Count(*buf, *pages as i64 - d.count)),
1172 Guard::Reserve { buf, pages } => Some(Early::Free(*buf, *pages as i64 + d.count + d.reserved)),
1173 Guard::SemWait { sem, until } if !d.set => match until {
1174 SemCond::AtLeast(n) => Some(Early::AtLeast(*sem, *n as i64 - d.sem)),
1175 SemCond::Equals(n) if d.sem == 0 => Some(Early::Equals(*sem, *n as i64)),
1176 SemCond::Equals(_) => None,
1177 },
1178 Guard::SemWait { .. } => None,
1179 Guard::Link { .. } => None,
1181 }
1182}
1183
1184#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1187enum Bound {
1188 End { var: LoopVar, count: usize, start: usize },
1189 Start { var: LoopVar, count: usize, end: usize },
1190}
1191
1192#[derive(Clone, Debug, PartialEq, Eq)]
1197struct Exit {
1198 bound: Option<Bound>,
1199 branches: Vec<(usize, Vec<(usize, Early)>)>,
1201}
1202
1203fn exit_of(micro: &[Micro], pc: usize, site: Site) -> Exit {
1204 let own = own_deltas(micro, pc, site);
1205 let after = after_effects(micro, pc);
1206 let checked = |at: usize| -> Vec<(usize, Early)> {
1207 let mut own = own.clone();
1208 let mut out = Vec::new();
1209 while let Some(Micro::Guarded(g)) = micro.get(at + out.len()) {
1210 let Some(e) = early_check(g, &own) else { break };
1211 if let Guard::Reserve { buf, pages } = g {
1212 own.entry(Resource::Fifo(*buf)).or_default().reserved += *pages as i64;
1213 }
1214 out.push((at + out.len(), e));
1215 }
1216 out
1217 };
1218 match micro.get(after) {
1219 Some(Micro::LoopEnd { var, count, start }) => Exit {
1220 bound: Some(Bound::End { var: *var, count: *count, start: *start }),
1221 branches: vec![(start + 1, checked(start + 1)), (after + 1, checked(after + 1))],
1222 },
1223 Some(Micro::LoopStart { var, count, end }) => Exit {
1224 bound: Some(Bound::Start { var: *var, count: *count, end: *end }),
1225 branches: vec![(after + 1, checked(after + 1)), (end + 1, Vec::new())],
1226 },
1227 _ => Exit { bound: None, branches: vec![(after, checked(after))] },
1228 }
1229}
1230
1231fn after_effects(micro: &[Micro], pc: usize) -> usize {
1233 let mut n = pc + 1;
1234 while matches!(micro.get(n), Some(Micro::Then(_))) {
1235 n += 1;
1236 }
1237 n
1238}
1239
1240fn plans(ctx: &Ctx) -> Result<Vec<EnginePlan>, SpatialLowerError> {
1241 let p = ctx.p;
1242 let sched = p
1243 .schedule
1244 .as_ref()
1245 .ok_or(SpatialLowerError::Unsupported("a scheduled program"))?;
1246 let pl = p
1247 .placement
1248 .as_ref()
1249 .ok_or(SpatialLowerError::Unsupported("a placed program"))?;
1250 let mut classes: Vec<String> = Vec::new();
1251 let mut out = Vec::new();
1252 for prog in &sched.programs {
1253 let sig = format!("{:?}:{}", prog.engine, p.engine_signature(prog));
1254 let class = match classes.iter().position(|c| *c == sig) {
1255 Some(i) => i,
1256 None => {
1257 classes.push(sig);
1258 classes.len() - 1
1259 }
1260 };
1261 let mut micro = Vec::new();
1262 lower_ops(ctx, &prog.ops, &mut micro)?;
1263 let physical = micro.iter().fold((0, 0), |acc, m| match m {
1264 Micro::Fixed(Fixed::Matmul { array, .. }) => (acc.0.max(array.0), acc.1.max(array.1)),
1265 _ => acc,
1266 });
1267 for m in &mut micro {
1268 if let Micro::Fixed(Fixed::Matmul { array, .. }) = m {
1269 *array = physical;
1270 }
1271 }
1272 let regions = engine::acc_regions(µ);
1273 for (m, region) in micro.iter_mut().zip(regions) {
1274 if let (Micro::Fixed(Fixed::AcquireAcc { cols, .. } | Fixed::PackAcc { cols, .. }), Some(r)) = (m, region) {
1275 *cols = r;
1276 }
1277 }
1278 fold_effects(ctx, &mut micro);
1279 let items = pl.items_at(prog.site);
1280 out.push(EnginePlan {
1281 site: prog.site,
1282 engine: prog.engine,
1283 class,
1284 micro,
1285 item_start: items.first().map(|i| i.idx()).unwrap_or(0),
1286 item_count: items.len(),
1287 });
1288 }
1289 engine::set_generic_depths(ctx, &mut out)?;
1290 Ok(out)
1291}
1292
1293#[derive(Clone, Default)]
1296struct FifoState {
1297 count: usize,
1298 reserved: usize,
1299}
1300
1301struct EngineState {
1302 pc: usize,
1303 ph: u64,
1304 lv: BTreeMap<LoopVar, usize>,
1305 done: bool,
1306}
1307
1308pub fn fabric_cycle_model(p: &SpatialProgram, target: &SpatialTarget) -> u64 {
1311 let Ok(ctx) = context(p, target) else { return 0 };
1312 let Ok(plans) = plans(&ctx) else { return 0 };
1313 twin(&ctx, &plans, &mut |_, _| {}, &mut Vec::new(), TWIN_LIMIT)
1314}
1315
1316const TWIN_LIMIT: u64 = 50_000_000;
1318
1319pub fn fabric_cycles_within(p: &SpatialProgram, target: &SpatialTarget, limit: u64) -> Option<u64> {
1322 let ctx = context(p, target).ok()?;
1323 let plans = plans(&ctx).ok()?;
1324 let cycles = twin(&ctx, &plans, &mut |_, _| {}, &mut Vec::new(), limit);
1325 (cycles <= limit).then_some(cycles)
1326}
1327
1328pub fn fabric_cycle_segments(p: &SpatialProgram, target: &SpatialTarget) -> Option<Vec<u64>> {
1332 let ctx = context(p, target).ok()?;
1333 let plans = plans(&ctx).ok()?;
1334 let mut halts = Vec::new();
1335 let total = twin(&ctx, &plans, &mut |_, _| {}, &mut halts, TWIN_LIMIT);
1336 halts.push(total);
1337 Some(halts)
1338}
1339
1340#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1342pub enum Activity {
1343 Busy(&'static str),
1345 Waiting(&'static str),
1347 Looping,
1349 Done,
1351}
1352
1353#[derive(Clone, Debug, PartialEq, Eq)]
1355pub struct EngineCycles {
1356 pub site: Site,
1357 pub engine: Engine,
1358 pub cycles: BTreeMap<Activity, u64>,
1359}
1360
1361#[derive(Clone, Debug, PartialEq, Eq)]
1364pub struct CycleBreakdown {
1365 pub total: u64,
1366 pub engines: Vec<EngineCycles>,
1367}
1368
1369pub fn fabric_cycle_breakdown(p: &SpatialProgram, target: &SpatialTarget) -> Option<CycleBreakdown> {
1372 let ctx = context(p, target).ok()?;
1373 let plans = plans(&ctx).ok()?;
1374 let mut engines: Vec<EngineCycles> =
1375 plans.iter().map(|pl| EngineCycles { site: pl.site, engine: pl.engine, cycles: BTreeMap::new() }).collect();
1376 let total = twin(&ctx, &plans, &mut |ei, a| *engines[ei].cycles.entry(a).or_default() += 1, &mut Vec::new(), TWIN_LIMIT);
1377 Some(CycleBreakdown { total, engines })
1378}
1379
1380pub fn fabric_micro_listing(p: &SpatialProgram, target: &SpatialTarget) -> Option<Vec<(Site, Engine, Vec<String>)>> {
1384 let ctx = context(p, target).ok()?;
1385 let plans = plans(&ctx).ok()?;
1386 Some(
1387 plans
1388 .iter()
1389 .map(|pl| {
1390 let regions = engine::acc_regions(&pl.micro);
1391 let lines = pl
1392 .micro
1393 .iter()
1394 .enumerate()
1395 .map(|(i, m)| match m {
1396 Micro::Fixed(f @ (Fixed::ReadTiles { .. } | Fixed::WriteTiles { .. })) => {
1397 format!("{} [{}] {f:?} run {}", fixed_kind(f), ctx.duration(f), ctx.run(f))
1398 }
1399 Micro::Fixed(f @ (Fixed::AcquireAcc { .. } | Fixed::PackAcc { .. })) => format!(
1400 "{} [{}] {f:?} region {} run {}",
1401 fixed_kind(f),
1402 ctx.duration(f),
1403 regions[i].unwrap_or(1),
1404 ctx.acc_run(f)
1405 ),
1406 Micro::Fixed(f @ Fixed::Generic { unit, .. }) => format!("{} [{}] {f:?} points: {}", fixed_kind(f), ctx.duration(f), unit.points()),
1407 Micro::Fixed(f) => format!("{} [{}] {f:?}", fixed_kind(f), ctx.duration(f)),
1408 Micro::Then(f) => format!(" then {f:?}"),
1409 Micro::Guarded(g) => format!("{} {g:?}", guard_kind(g)),
1410 Micro::LoopStart { var, count, .. } => format!("loop {var:?} × {count}"),
1411 Micro::LoopEnd { var, .. } => format!("end loop {var:?}"),
1412 })
1413 .collect();
1414 (pl.site, pl.engine, lines)
1415 })
1416 .collect(),
1417 )
1418}
1419
1420fn fixed_kind(f: &Fixed) -> &'static str {
1422 match f {
1423 Fixed::ReadTiles { .. } => "read tiles",
1424 Fixed::WriteTiles { .. } => "write tiles",
1425 Fixed::Push { .. } => "push",
1426 Fixed::Pop { .. } => "pop",
1427 Fixed::Barrier => "barrier",
1428 Fixed::AcquireAcc { .. } => "acquire",
1429 Fixed::ReleaseAcc => "release",
1430 Fixed::Matmul { .. } => "matmul",
1431 Fixed::Unary { .. } => "unary",
1432 Fixed::Generic { .. } => "generic",
1433 Fixed::PackAcc { .. } => "pack",
1434 Fixed::SemSet { .. } | Fixed::SemInc { .. } => "semaphore",
1435 Fixed::McastSetValid { .. } | Fixed::McastSend { .. } => "multicast",
1436 }
1437}
1438
1439fn guard_kind(g: &Guard) -> &'static str {
1440 match g {
1441 Guard::Reserve { .. } => "reserve",
1442 Guard::Wait { .. } => "wait",
1443 Guard::SemWait { .. } => "semaphore wait",
1444 Guard::Link { .. } => "link",
1445 }
1446}
1447
1448fn twin(ctx: &Ctx, plans: &[EnginePlan], observe: &mut dyn FnMut(usize, Activity), halts: &mut Vec<u64>, limit: u64) -> u64 {
1451 let p = ctx.p;
1452 let mut fifos: BTreeMap<BufferId, FifoState> = p
1453 .buffers
1454 .iter()
1455 .map(|b| (b.id, FifoState::default()))
1456 .collect();
1457 let mut sems: BTreeMap<(SemId, Site), usize> = BTreeMap::new();
1458 for s in &p.semaphores {
1459 for site in &s.sites {
1460 sems.insert((s.id, *site), s.init as usize);
1461 }
1462 }
1463 let mut states: Vec<EngineState> = plans
1464 .iter()
1465 .map(|_| EngineState {
1466 pc: 0,
1467 ph: 0,
1468 lv: BTreeMap::new(),
1469 done: false,
1470 })
1471 .collect();
1472 let receivers_of = |g: GroupId| -> Vec<Site> {
1473 let pl = p.placement.as_ref().unwrap();
1474 let gr = &pl.groups[g.idx()];
1475 gr.rect
1476 .sites()
1477 .into_iter()
1478 .filter(|s| *s != gr.sender)
1479 .collect()
1480 };
1481 let mut cycles: u64 = 2;
1484 loop {
1485 if states.iter().all(|s| s.done) || cycles > limit {
1486 break;
1487 }
1488 let mut fifo_delta: BTreeMap<BufferId, (i64, i64)> = BTreeMap::new(); let mut sem_inc: BTreeMap<(SemId, Site), usize> = BTreeMap::new();
1491 let mut sem_set: BTreeMap<(SemId, Site), usize> = BTreeMap::new();
1492 let mut next: Vec<Option<(usize, u64, Option<(LoopVar, usize)>)>> = Vec::new();
1493 for (ei, plan) in plans.iter().enumerate() {
1494 let st = &states[ei];
1495 if st.done {
1496 observe(ei, Activity::Done);
1497 next.push(None);
1498 continue;
1499 }
1500 let Some(m) = plan.micro.get(st.pc) else {
1501 observe(ei, Activity::Done);
1502 next.push(Some((st.pc, 0, None)));
1503 continue;
1504 };
1505 observe(
1506 ei,
1507 match m {
1508 Micro::Fixed(f) | Micro::Then(f) => Activity::Busy(fixed_kind(f)),
1509 Micro::LoopStart { .. } | Micro::LoopEnd { .. } => Activity::Looping,
1510 Micro::Guarded(g) => {
1511 let open = match g {
1512 Guard::Reserve { buf, pages } => {
1513 let f = &fifos[buf];
1514 ctx.depth(*buf) >= f.count + f.reserved + *pages
1515 }
1516 Guard::Wait { buf, pages } => fifos[buf].count >= *pages,
1517 Guard::SemWait { sem, until } => {
1518 let v = *sems.get(&(*sem, plan.site)).unwrap_or(&0);
1519 match until {
1520 SemCond::Equals(n) => v == *n,
1521 SemCond::AtLeast(n) => v >= *n,
1522 }
1523 }
1524 Guard::Link { .. } => true,
1525 };
1526 if open {
1527 Activity::Busy(guard_kind(g))
1528 } else {
1529 Activity::Waiting(guard_kind(g))
1530 }
1531 }
1532 },
1533 );
1534 let open = |g: &Guard| match g {
1535 Guard::Reserve { buf, pages } => {
1536 let f = &fifos[buf];
1537 ctx.depth(*buf) >= f.count + f.reserved + *pages
1538 }
1539 Guard::Wait { buf, pages } => fifos[buf].count >= *pages,
1540 Guard::SemWait { sem, until } => {
1541 let v = *sems.get(&(*sem, plan.site)).unwrap_or(&0);
1542 match until {
1543 SemCond::Equals(n) => v == *n,
1544 SemCond::AtLeast(n) => v >= *n,
1545 }
1546 }
1547 Guard::Link { .. } => true,
1550 };
1551 if let Micro::Guarded(Guard::Link { .. }) = m {
1554 halts.push(cycles - 1);
1555 }
1556 let step = match m {
1557 Micro::Guarded(g) => {
1558 let ok = open(g);
1559 if ok {
1560 if let Guard::Reserve { buf, pages } = g {
1561 fifo_delta.entry(*buf).or_default().1 += *pages as i64;
1562 }
1563 Some((st.pc + 1, 0, None))
1564 } else {
1565 Some((st.pc, 0, None))
1566 }
1567 }
1568 Micro::Fixed(f) => {
1569 let d = ctx.duration(f);
1570 if st.ph + 1 >= d {
1571 let next = after_effects(&plan.micro, st.pc);
1573 let folded = plan.micro[st.pc + 1..next].iter().filter_map(|m| match m {
1574 Micro::Then(e) => Some(e),
1575 _ => None,
1576 });
1577 for e in std::iter::once(f).chain(folded) {
1578 match e {
1579 Fixed::Push { buf, pages } => {
1580 let e = fifo_delta.entry(*buf).or_default();
1581 e.0 += *pages as i64;
1582 e.1 -= *pages as i64;
1583 }
1584 Fixed::Pop { buf, pages } => fifo_delta.entry(*buf).or_default().0 -= *pages as i64,
1585 Fixed::SemSet { sem, value } => {
1586 let v = match value {
1587 SemValue::Invalid => 0,
1588 SemValue::Valid => 1,
1589 SemValue::Count(n) => *n,
1590 };
1591 sem_set.insert((*sem, plan.site), v);
1592 }
1593 Fixed::SemInc { sem, at, by } => *sem_inc.entry((*sem, *at)).or_default() += *by,
1594 Fixed::McastSetValid { sem, group } => {
1595 for r in receivers_of(*group) {
1596 sem_set.insert((*sem, r), 1);
1597 }
1598 }
1599 _ => {}
1600 }
1601 }
1602 let exit = exit_of(&plan.micro, st.pc, plan.site);
1604 let count = |var: LoopVar, count: usize| if var == LV_ITEM { plan.item_count } else { count };
1605 let (branch, lv) = match exit.bound {
1606 None => (0, None),
1607 Some(Bound::End { var, count: c, .. }) => {
1608 let v = st.lv.get(&var).copied().unwrap_or(0) + 1;
1609 (if v < count(var, c) { 0 } else { 1 }, Some((var, v)))
1610 }
1611 Some(Bound::Start { var, count: c, .. }) => (if count(var, c) == 0 { 1 } else { 0 }, Some((var, 0))),
1612 };
1613 let (at, checks) = &exit.branches[branch];
1614 let holds = |e: &Early| match *e {
1615 Early::Count(b, n) => fifos[&b].count as i64 >= n,
1616 Early::Free(b, n) => {
1617 let f = &fifos[&b];
1618 ctx.depth(b) as i64 - f.count as i64 - f.reserved as i64 >= n
1619 }
1620 Early::AtLeast(sem, n) => *sems.get(&(sem, plan.site)).unwrap_or(&0) as i64 >= n,
1621 Early::Equals(sem, n) => *sems.get(&(sem, plan.site)).unwrap_or(&0) as i64 == n,
1622 };
1623 if !checks.is_empty() && checks.iter().all(|(_, e)| holds(e)) {
1624 for (g, _) in checks {
1625 if let Micro::Guarded(Guard::Reserve { buf, pages }) = &plan.micro[*g] {
1626 fifo_delta.entry(*buf).or_default().1 += *pages as i64;
1627 }
1628 }
1629 Some((checks[checks.len() - 1].0 + 1, 0, lv))
1630 } else {
1631 Some((*at, 0, lv))
1632 }
1633 } else {
1634 Some((st.pc, st.ph + 1, None))
1635 }
1636 }
1637 Micro::Then(_) => Some((st.pc + 1, 0, None)),
1639 Micro::LoopStart { var, count, end } => {
1640 if *count == 0 {
1641 Some((end + 1, 0, None))
1642 } else {
1643 Some((st.pc + 1, 0, Some((*var, 0))))
1644 }
1645 }
1646 Micro::LoopEnd { var, count, start } => {
1647 let v = st.lv.get(var).copied().unwrap_or(0) + 1;
1648 let count = if *var == LV_ITEM {
1649 plan.item_count
1650 } else {
1651 *count
1652 };
1653 if v < count {
1654 Some((start + 1, 0, Some((*var, v))))
1655 } else {
1656 Some((st.pc + 1, 0, Some((*var, v))))
1657 }
1658 }
1659 };
1660 next.push(step);
1661 }
1662 for (ei, step) in next.into_iter().enumerate() {
1664 if let Some((pc, ph, lv)) = step {
1665 let st = &mut states[ei];
1666 st.pc = pc;
1667 st.ph = ph;
1668 if let Some((v, x)) = lv {
1669 st.lv.insert(v, x);
1670 }
1671 if pc >= plans[ei].micro.len() {
1672 st.done = true;
1673 }
1674 }
1675 }
1676 for (b, (dc, dr)) in fifo_delta {
1677 let f = fifos.get_mut(&b).unwrap();
1678 f.count = (f.count as i64 + dc).max(0) as usize;
1679 f.reserved = (f.reserved as i64 + dr).max(0) as usize;
1680 }
1681 for (k, v) in sem_set {
1682 sems.insert(k, v);
1683 }
1684 for (k, n) in sem_inc {
1685 *sems.entry(k).or_insert(0) += n;
1686 }
1687 cycles += 1;
1688 }
1689 cycles
1690}
1691
1692fn context<'a>(
1693 p: &'a SpatialProgram,
1694 target: &'a SpatialTarget,
1695) -> Result<Ctx<'a>, SpatialLowerError> {
1696 let plan = p
1697 .work
1698 .plan
1699 .ok_or(SpatialLowerError::Unsupported("a tiled program"))?;
1700 let lat = target
1701 .site
1702 .local_mem_read_latency
1703 .ok_or(SpatialLowerError::Unsupported("a fabric target that states its local memory's read latency"))?;
1704 if !p.mem_banks.is_power_of_two() {
1705 return Err(SpatialLowerError::Unsupported("memory banks that are not a power of two"));
1706 }
1707 if !target.site.vector_lanes.is_power_of_two() {
1708 return Err(SpatialLowerError::Unsupported("a site whose vector unit has lanes that are not a power of two"));
1709 }
1710 Ok(Ctx {
1711 p,
1712 te: p.tile.elems(),
1713 tr: p.tile.rows,
1714 tc: p.tile.cols,
1715 grid_nx: plan.grid.1,
1716 plan_acc: plan.acc,
1717 lat: lat as u64,
1718 ranges: program_ranges(p),
1719 banks: p.mem_banks,
1720 lanes: target.site.vector_lanes.min(p.mem_banks),
1721 extent: p.page_extents(),
1722 phase_flags: Default::default(),
1723 halts: halts_of(p)?,
1724 multiplier: target.site.multiplier,
1725 pitch: p.tensors.iter().map(|t| tensor_pitch(p, t)).collect(),
1726 page_pitch: page_pitches(p),
1727 })
1728}
1729
1730fn halts_of(p: &SpatialProgram) -> Result<Vec<(SemId, usize)>, SpatialLowerError> {
1733 let mut out = Vec::new();
1734 for (i, l) in p.links.iter().enumerate() {
1735 let Some(k) = l.after.filter(|k| k + 1 < p.sections.len()) else { continue };
1736 let r = p.sections[k]
1737 .barrier_after
1738 .ok_or(SpatialLowerError::Unsupported("a link's halt after a section with no barrier after it"))?;
1739 out.push((r.gather, i));
1740 }
1741 Ok(out)
1742}
1743
1744fn tensor_width(t: &TensorDecl, ranges: &ProgramRanges) -> u32 {
1748 match t.role {
1749 TensorRole::Input | TensorRole::State => precision_int_bits(t.dtype).unwrap_or(64),
1750 TensorRole::Param | TensorRole::Activation | TensorRole::Output => ranges.tensors[t.id.idx()].map_or(1, signed_bits),
1751 }
1752}
1753
1754fn lanes(width: u32) -> Vec<(u32, u32)> {
1757 (0..width.div_ceil(32)).map(|l| (l * 32, (width - l * 32).min(32))).collect()
1758}
1759
1760fn lane(mem: &str, l: usize, n: usize) -> String {
1762 if n == 1 {
1763 mem.to_string()
1764 } else {
1765 format!("{mem}_l{l}")
1766 }
1767}
1768
1769fn bank(mem: &str, b: usize, banks: usize) -> String {
1771 if banks == 1 {
1772 mem.to_string()
1773 } else {
1774 format!("{mem}_b{b}")
1775 }
1776}
1777
1778fn log2_banks(banks: usize) -> u32 {
1780 banks.trailing_zeros()
1781}
1782
1783fn memory_word(mem: &str, addr: usize, width: u32, banks: usize) -> String {
1786 let m = bank(mem, addr % banks, banks);
1787 let row = addr / banks;
1788 let n = lanes(width).len();
1789 let parts: Vec<String> = (0..n).rev().map(|l| format!("{}[{row}]", lane(&m, l, n))).collect();
1790 format!("{{{}}}", parts.join(", "))
1791}
1792
1793fn resize_lanes(src: &str, from: u32, to: u32, banks: usize) -> String {
1797 let parts: Vec<String> = (0..banks)
1798 .rev()
1799 .map(|j| {
1800 let lo = j as u32 * from;
1801 if to > from {
1802 format!("{{{{{}{{{src}[{}]}}}}, {src}[{}:{lo}]}}", to - from, lo + from - 1, lo + from - 1)
1803 } else {
1804 format!("{src}[{}:{lo}]", lo + to - 1)
1805 }
1806 })
1807 .collect();
1808 format!("{{{}}}", parts.join(", "))
1809}
1810
1811#[allow(clippy::too_many_arguments)]
1818fn memory_write(tag: &str, mem: &str, addr: &str, n: &str, lw: &str, data: &str, hi: &str, width: u32, banks: usize, runs: &BTreeSet<usize>) -> (String, String) {
1819 let ls = lanes(width);
1820 let put = |m: &str, a: &str, v: &str| -> String {
1821 ls.iter()
1822 .enumerate()
1823 .map(|(l, (lo, w))| format!("{}[{a}] <= {v}[{}:{lo}];", lane(m, l, ls.len()), lo + w - 1))
1824 .collect::<Vec<_>>()
1825 .join(" ")
1826 };
1827 if banks == 1 {
1828 return (String::new(), put(mem, addr, data));
1829 }
1830 let lg = log2_banks(banks);
1831 let mut wires = format!(
1832 " wire [31:0] {tag}_a = {addr}; wire [31:0] {tag}_r = {tag}_a >> {lg}; wire [{}:0] {tag}_o = {tag}_a[{}:0]; wire [{}:0] {tag}_v = {data}; wire [{}:0] {tag}_h = {hi};",
1833 lg - 1,
1834 lg - 1,
1835 width - 1,
1836 (banks as u32 - 1) * width - 1
1837 );
1838 let lane_of = |j: usize| if j == 0 { format!("{tag}_v") } else { format!("{tag}_h[{}:{}]", j as u32 * width - 1, (j as u32 - 1) * width) };
1839 let arm = |b: usize, w: u32| -> (String, String) {
1841 let j = b & ((1usize << w) - 1);
1842 let word = if w == lg { "1'b1".to_string() } else { format!("({tag}_o[{}:{w}] == {}'d{})", lg - 1, lg - w, b >> w) };
1843 (lane_of(j), format!("{word} && ({}'d{j} < {n})", lg + 1))
1844 };
1845 let carried: Vec<u32> = runs.iter().map(|w| w.trailing_zeros()).collect();
1846 let mut stmts = Vec::new();
1847 for b in 0..banks {
1848 if let [w] = carried[..] {
1849 let (d, e) = arm(b, w);
1850 wires.push_str(&format!(" wire [{}:0] {tag}_d{b} = {d}; wire {tag}_e{b} = {e};", width - 1));
1851 } else {
1852 let arms: String = carried
1853 .iter()
1854 .map(|w| {
1855 let (d, e) = arm(b, *w);
1856 format!(" 4'd{w}: begin {tag}_d{b} = {d}; {tag}_e{b} = {e}; end")
1857 })
1858 .collect();
1859 wires.push_str(&format!(
1860 " reg [{}:0] {tag}_d{b}; reg {tag}_e{b}; always @* case ({lw}){arms} default: begin {tag}_d{b} = {tag}_v; {tag}_e{b} = 1'b0; end endcase",
1861 width - 1
1862 ));
1863 }
1864 stmts.push(format!("if ({tag}_e{b}) begin {} end", put(&bank(mem, b, banks), &format!("{tag}_r"), &format!("{tag}_d{b}"))));
1865 }
1866 let known = carried.iter().map(|w| format!("{lw} == 4'd{w}")).collect::<Vec<_>>().join(" || ");
1867 stmts.push(format!(
1868 "\n// synthesis translate_off\nif (!({known}) || ({tag}_o & ((5'd1 << {lw}) - 5'd1)) != 0) begin $display(\"FATAL: {tag}: a run of 2^%0d from element %0d is not on its word, or not a width the port carries\", {lw}, {tag}_a); $finish; end\n// synthesis translate_on\n"
1869 ));
1870 (wires, stmts.join(" "))
1871}
1872fn tensor_hex(t: &TensorDecl, words: &[i64], width: u32, banks: usize) -> Vec<(String, String)> {
1875 let ls = lanes(width);
1876 let rows = words.len().max(1).div_ceil(banks);
1877 let mut out = Vec::new();
1878 for b in 0..banks {
1879 for (l, (lo, w)) in ls.iter().enumerate() {
1880 let stem = bank(&format!("t{}", t.id.idx()), b, banks);
1881 let name = if ls.len() == 1 { format!("{stem}.hex") } else { format!("{stem}_l{l}.hex") };
1882 let digits = (*w as usize).div_ceil(4);
1883 let mask = if *w >= 64 { u64::MAX } else { (1u64 << w) - 1 };
1884 let mut body = String::with_capacity(rows * (digits + 1));
1885 for r in 0..rows {
1886 let x = words.get(r * banks + b).copied().unwrap_or(0);
1887 body.push_str(&format!("{:0digits$x}\n", ((x as u64) >> lo) & mask));
1888 }
1889 out.push((name, body));
1890 }
1891 }
1892 out
1893}
1894
1895fn cycle_floor(ctx: &Ctx, plans: &[EnginePlan]) -> u64 {
1897 let mut per_site: BTreeMap<Site, u64> = BTreeMap::new();
1898 for plan in plans {
1899 if plan.engine != Engine::Compute {
1900 continue;
1901 }
1902 let mut total = 0u64;
1903 let mut mult: Vec<u64> = vec![1];
1904 for m in &plan.micro {
1905 match m {
1906 Micro::LoopStart { var, count, .. } => {
1907 let c = if *var == LV_ITEM {
1908 plan.item_count
1909 } else {
1910 *count
1911 };
1912 let top = *mult.last().unwrap();
1913 mult.push(top * c as u64);
1914 }
1915 Micro::LoopEnd { .. } => {
1916 mult.pop();
1917 }
1918 Micro::Fixed(f @ (Fixed::Matmul { .. } | Fixed::Unary { .. })) => {
1919 total += ctx.duration(f) * *mult.last().unwrap()
1920 }
1921 _ => {}
1922 }
1923 }
1924 *per_site.entry(plan.site).or_default() += total;
1925 }
1926 per_site.values().copied().max().unwrap_or(0)
1927}
1928
1929pub fn host_word(p: &SpatialProgram, t: &TensorDecl, i: usize, j: usize) -> usize {
1935 word_at(p, tensor_pitch(p, t), i, j)
1936}
1937
1938fn word_at(p: &SpatialProgram, pitch: usize, i: usize, j: usize) -> usize {
1940 let (tr, tc) = (p.tile.rows, p.tile.cols);
1941 let tile = (i / tr) * pitch + j / tc;
1942 tile * tr * tc + (i % tr) * tc + j % tc
1943}
1944
1945fn tensor_words(t: &TensorDecl, pitch: usize, te: usize) -> usize {
1947 t.tiles.rows_t * pitch * te
1948}
1949
1950fn tiled_words(
1953 p: &SpatialProgram,
1954 t: &TensorDecl,
1955 values: &dyn Fn(usize, usize) -> i64,
1956) -> Vec<i64> {
1957 let pitch = tensor_pitch(p, t);
1958 let mut out = vec![0i64; tensor_words(t, pitch, p.tile.elems())];
1959 for i in 0..t.elems.rows {
1960 for j in 0..t.elems.cols {
1961 out[word_at(p, pitch, i, j)] = values(i, j);
1962 }
1963 }
1964 out
1965}
1966
1967fn row_word(width: usize, banks: usize) -> usize {
1972 width.next_power_of_two().min(banks).max(1)
1973}
1974
1975pub(crate) fn tensor_pitch(p: &SpatialProgram, t: &TensorDecl) -> usize {
1987 let cols = t.tiles.cols_t;
1988 let word = row_word(cols, p.mem_banks);
1989 if p.tile.elems() != 1 || p.mem_banks <= 1 || t.tiles.rows_t <= 1 || cols % word == 0 {
1990 return cols;
1991 }
1992 let splits = tensor_walks(p).into_iter().all(|(op, loops, section)| match op {
1993 ChoreoOp::ReadTiles { tensor, at, block_h, block_w, stride_w, stride_h, .. }
1994 | ChoreoOp::WriteTiles { tensor, at, block_h, block_w, stride_w, stride_h, .. } => {
1995 let coords: Vec<(usize, usize)> = p
1996 .work
1997 .items
1998 .iter()
1999 .filter(|it| section.is_none() || p.section_of_item(it.id) == section)
2000 .map(|it| it.coord)
2001 .collect();
2002 *tensor != t.id || walk_splits(at, (*block_h, *block_w, *stride_w, *stride_h), &loops, &coords, cols)
2003 }
2004 _ => true,
2005 });
2006 if splits {
2007 cols.next_multiple_of(word)
2008 } else {
2009 cols
2010 }
2011}
2012
2013fn tensor_walks(p: &SpatialProgram) -> Vec<(&ChoreoOp, BTreeMap<LoopVar, usize>, Option<usize>)> {
2016 fn visit<'a>(
2017 p: &SpatialProgram,
2018 ops: &'a [TaggedOp],
2019 loops: &mut Vec<(LoopVar, usize)>,
2020 out: &mut Vec<(&'a ChoreoOp, BTreeMap<LoopVar, usize>, Option<usize>)>,
2021 ) {
2022 for t in ops {
2023 match &t.op {
2024 ChoreoOp::ReadTiles { .. } | ChoreoOp::WriteTiles { .. } => {
2025 out.push((&t.op, loops.iter().copied().collect(), p.section_of_stage(t.stage)))
2026 }
2027 ChoreoOp::Loop { var, count, body } => {
2028 loops.push((*var, count.bound()));
2029 visit(p, body, loops, out);
2030 loops.pop();
2031 }
2032 _ => {}
2033 }
2034 }
2035 }
2036 let mut out = Vec::new();
2037 for prog in p.schedule.iter().flat_map(|s| &s.programs) {
2038 visit(p, &prog.ops, &mut Vec::new(), &mut out);
2039 }
2040 out
2041}
2042
2043fn walk_splits(
2049 at: &TileAddr,
2050 (block_h, block_w, stride_w, stride_h): (usize, usize, usize, usize),
2051 loops: &BTreeMap<LoopVar, usize>,
2052 coords: &[(usize, usize)],
2053 cols: usize,
2054) -> bool {
2055 let col = |s: usize| s % cols;
2056 let mut reach = col(at.base) + block_h.saturating_sub(1) * col(stride_h) + block_w.saturating_sub(1) * col(stride_w);
2057 for (v, s) in &at.steps {
2058 let top = if *v == LV_ITEM_Y {
2059 coords.iter().map(|c| c.0).max().unwrap_or(0)
2060 } else if *v == LV_ITEM_X {
2061 coords.iter().map(|c| c.1).max().unwrap_or(0)
2062 } else {
2063 loops.get(v).map_or(0, |c| c.saturating_sub(1))
2064 };
2065 reach += top * col(*s);
2066 }
2067 if let Some(ind) = at.indirect {
2068 reach += ind.bound.1 / ind.elems_per_tile.max(1) * col(ind.tile_stride);
2069 }
2070 reach < cols
2071}
2072
2073fn pitched(s: usize, cols: usize, pitch: usize) -> usize {
2076 s / cols * pitch + s % cols
2077}
2078
2079fn pitched_addr(at: &TileAddr, cols: usize, pitch: usize) -> TileAddr {
2082 TileAddr {
2083 base: pitched(at.base, cols, pitch),
2084 steps: at.steps.iter().map(|(v, s)| (*v, pitched(*s, cols, pitch))).collect(),
2085 indirect: at.indirect.map(|ind| IndirectTile { tile_stride: pitched(ind.tile_stride, cols, pitch), ..ind }),
2086 }
2087}
2088
2089fn page_pitches(p: &SpatialProgram) -> BTreeMap<BufferId, (usize, usize)> {
2095 #[derive(Default)]
2096 struct Use {
2097 widths: BTreeSet<usize>,
2098 rows: usize,
2099 other: bool,
2100 }
2101 fn visit(p: &SpatialProgram, ops: &[TaggedOp], uses: &mut BTreeMap<BufferId, Use>) {
2102 let tp = |b: BufferId| p.buffer(b).origin.unwrap_or(b);
2103 let other = |b: BufferId, uses: &mut BTreeMap<BufferId, Use>| uses.entry(tp(b)).or_default().other = true;
2104 for t in ops {
2105 match &t.op {
2106 ChoreoOp::Reserve { .. } | ChoreoOp::Push { .. } | ChoreoOp::Wait { .. } | ChoreoOp::Pop { .. } => {}
2107 ChoreoOp::ReadTiles { at, block_h, block_w, stride_w, into, .. } => {
2108 let u = uses.entry(tp(*into)).or_default();
2109 u.widths.insert(*block_w);
2110 u.rows = u.rows.max(*block_h);
2111 u.other |= *stride_w != 1;
2112 if let Some(ind) = at.indirect {
2113 other(ind.index, uses);
2114 }
2115 }
2116 ChoreoOp::WriteTiles { from, at, .. } => {
2117 other(*from, uses);
2118 if let Some(ind) = at.indirect {
2119 other(ind.index, uses);
2120 }
2121 }
2122 ChoreoOp::Compute { op, ins, acc_reload } => match op.shape() {
2123 Ok(ComputeShape::Tiles { block, sub_blocks, .. }) if ins.len() == 2 => {
2124 other(ins[0], uses);
2125 let u = uses.entry(tp(ins[1])).or_default();
2126 u.widths.insert(sub_blocks.1 * block.osw);
2127 u.rows = u.rows.max(block.ibw);
2128 if let Some(r) = acc_reload {
2129 other(*r, uses);
2130 }
2131 }
2132 _ => {
2133 for b in ins.iter().chain(acc_reload) {
2134 other(*b, uses);
2135 }
2136 }
2137 },
2138 ChoreoOp::PackAcc { to, .. } => other(*to, uses),
2139 ChoreoOp::McastSend { buf, .. } => other(*buf, uses),
2140 ChoreoOp::Loop { body, .. } => visit(p, body, uses),
2141 ChoreoOp::ReadBarrier
2142 | ChoreoOp::WriteBarrier
2143 | ChoreoOp::McastSetValid { .. }
2144 | ChoreoOp::Flush { .. }
2145 | ChoreoOp::SemSet { .. }
2146 | ChoreoOp::SemInc { .. }
2147 | ChoreoOp::SemWait { .. }
2148 | ChoreoOp::AcquireAcc { .. }
2149 | ChoreoOp::ReleaseAcc
2150 | ChoreoOp::Barrier { .. } => {}
2151 }
2152 }
2153 }
2154 let mut uses: BTreeMap<BufferId, Use> = BTreeMap::new();
2155 if p.tile.elems() != 1 || p.mem_banks <= 1 {
2156 return BTreeMap::new();
2157 }
2158 for prog in p.schedule.iter().flat_map(|s| &s.programs) {
2159 visit(p, &prog.ops, &mut uses);
2160 }
2161 for st in p.stages.iter().filter(|st| matches!(st.kind, StageKind::Recv { .. })) {
2162 for b in &st.outs {
2163 uses.entry(p.buffer(*b).origin.unwrap_or(*b)).or_default().other = true;
2164 }
2165 }
2166 let extents = p.page_extents();
2167 uses.into_iter()
2168 .filter_map(|(tpl, u)| {
2169 let [w] = u.widths.iter().copied().collect::<Vec<_>>()[..] else { return None };
2170 let word = row_word(w, p.mem_banks);
2171 let whole_rows = p.buffers.iter().filter(|b| b.origin.unwrap_or(b.id) == tpl).all(|b| extents[b.id.idx()] % w == 0);
2172 (!u.other && u.rows > 1 && w % word != 0 && whole_rows).then(|| (tpl, (w, w.next_multiple_of(word))))
2173 })
2174 .collect()
2175}
2176
2177pub fn input_hex_files(p: &SpatialProgram, inputs: &Inputs) -> Vec<(String, String)> {
2179 let ranges = program_ranges(p);
2180 let mut out = Vec::new();
2181 for t in &p.tensors {
2182 if t.role == TensorRole::Output {
2183 continue;
2184 }
2185 let Some(v) = inputs.tensors.get(&t.id) else {
2186 continue;
2187 };
2188 let cols = t.elems.cols;
2189 let words = match v {
2190 Value::Int(x) => tiled_words(p, t, &|i, j| x.get(i * cols + j).copied().unwrap_or(0)),
2191 Value::Float(x) => tiled_words(p, t, &|i, j| {
2192 x.get(i * cols + j).copied().unwrap_or(0.0) as i64
2193 }),
2194 };
2195 out.extend(tensor_hex(t, &words, tensor_width(t, &ranges), p.mem_banks));
2196 }
2197 out
2198}
2199
2200fn bits_for(n: usize) -> usize {
2203 (usize::BITS - n.max(2).saturating_sub(1).leading_zeros()).max(1) as usize
2204}
2205
2206fn var_name(v: LoopVar) -> String {
2207 format!("lv{}", v.0)
2208}
2209
2210fn ph_less(by: u64) -> String {
2212 if by == 0 {
2213 "ph".into()
2214 } else {
2215 format!("(ph - {by})")
2216 }
2217}
2218
2219fn from_phase(ctx: &Ctx, from: u64, body: &str) -> String {
2221 if from == 0 {
2222 body.to_string()
2223 } else {
2224 format!("if ({}) begin {body} end", ctx.ph_ge(from))
2225 }
2226}
2227
2228fn addr_v(at: &TileAddr, bound: &BTreeSet<LoopVar>, (oy, ox): (&str, &str), ctx: &Ctx) -> String {
2232 let mut terms = vec![format!("{}", at.base)];
2233 if let Some(ind) = at.indirect {
2234 let ki = ctx.template(ind.index).idx();
2235 terms.push(format!(
2236 "(($unsigned(f{ki}_rd_data) / {}) * {})",
2237 ind.elems_per_tile.max(1),
2238 ind.tile_stride
2239 ));
2240 }
2241 for (v, stride) in &at.steps {
2242 if *stride == 0 {
2243 continue;
2244 }
2245 let name = if *v == LV_ITEM_Y {
2246 oy.to_string()
2247 } else if *v == LV_ITEM_X {
2248 ox.to_string()
2249 } else if bound.contains(v) {
2250 var_name(*v)
2251 } else {
2252 continue;
2253 };
2254 terms.push(format!("{name} * {stride}"));
2255 }
2256 terms.join(" + ")
2257}
2258
2259struct Emit {
2260 s: String,
2261}
2262
2263impl Emit {
2264 fn l(&mut self, line: impl AsRef<str>) {
2265 self.s.push_str(line.as_ref());
2266 self.s.push('\n');
2267 }
2268}
2269
2270#[derive(Default)]
2272struct BufUse {
2273 produces: BTreeSet<BufferId>,
2275 consumes: BTreeSet<BufferId>,
2277 writes: BTreeSet<BufferId>,
2278 reads: BTreeSet<BufferId>,
2279}
2280
2281impl BufUse {
2282 fn all(&self) -> BTreeSet<BufferId> {
2283 self.produces
2284 .iter()
2285 .chain(&self.consumes)
2286 .chain(&self.writes)
2287 .chain(&self.reads)
2288 .copied()
2289 .collect()
2290 }
2291}
2292
2293fn buffer_uses(micro: &[Micro]) -> BufUse {
2294 let mut u = BufUse::default();
2295 for m in micro {
2296 match m {
2297 Micro::Guarded(Guard::Reserve { buf, .. }) => {
2298 u.produces.insert(*buf);
2299 }
2300 Micro::Guarded(Guard::Wait { buf, .. }) => {
2301 u.consumes.insert(*buf);
2302 }
2303 Micro::Fixed(f) | Micro::Then(f) => match f {
2304 Fixed::Push { buf, .. } => {
2305 u.produces.insert(*buf);
2306 }
2307 Fixed::Pop { buf, .. } => {
2308 u.consumes.insert(*buf);
2309 }
2310 Fixed::ReadTiles { into, at, .. } => {
2311 u.writes.insert(*into);
2312 if let Some(ind) = at.indirect {
2313 u.reads.insert(ind.index);
2314 }
2315 }
2316 Fixed::WriteTiles { from, at, .. } => {
2317 u.reads.insert(*from);
2318 if let Some(ind) = at.indirect {
2319 u.reads.insert(ind.index);
2320 }
2321 }
2322 Fixed::Matmul { ins, reload, .. } => {
2323 u.reads.insert(ins[0]);
2324 u.reads.insert(ins[1]);
2325 if let Some(r) = reload {
2326 u.reads.insert(*r);
2327 }
2328 }
2329 Fixed::Unary { ins, .. } | Fixed::Generic { ins, .. } => {
2330 for b in ins {
2331 u.reads.insert(*b);
2332 }
2333 }
2334 Fixed::PackAcc { to, accumulate, .. } => {
2335 u.writes.insert(*to);
2336 if *accumulate {
2337 u.reads.insert(*to);
2339 }
2340 }
2341 Fixed::McastSend { buf, .. } => {
2342 u.reads.insert(*buf);
2343 }
2344 _ => {}
2345 },
2346 _ => {}
2347 }
2348 }
2349 u
2350}
2351
2352#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
2354enum Port {
2355 FifoWr(BufferId),
2356 FifoRd(BufferId),
2357 TensorWr(TensorId),
2358 TensorRd(TensorId),
2359}
2360
2361fn port_widths(ctx: &Ctx, micro: &[Micro]) -> BTreeMap<Port, BTreeSet<usize>> {
2365 fn put(out: &mut BTreeMap<Port, BTreeSet<usize>>, port: Port, w: usize) {
2366 out.entry(port).or_default().insert(w);
2367 }
2368 let mut out = BTreeMap::new();
2369 for m in micro {
2370 let (Micro::Fixed(f) | Micro::Then(f)) = m else { continue };
2371 match f {
2372 Fixed::ReadTiles { tensor, at, into, .. } => {
2373 let w = ctx.run(f);
2374 put(&mut out, Port::TensorRd(*tensor), w);
2375 put(&mut out, Port::FifoWr(*into), w);
2376 if let Some(ind) = at.indirect {
2377 put(&mut out, Port::FifoRd(ind.index), 1);
2378 }
2379 }
2380 Fixed::WriteTiles { from, tensor, at, .. } => {
2381 let w = ctx.run(f);
2382 put(&mut out, Port::FifoRd(*from), w);
2383 put(&mut out, Port::TensorWr(*tensor), w);
2384 if let Some(ind) = at.indirect {
2385 put(&mut out, Port::FifoRd(ind.index), 1);
2386 }
2387 }
2388 Fixed::PackAcc { to, accumulate, .. } => {
2389 let w = ctx.acc_run(f);
2390 put(&mut out, Port::FifoWr(*to), w);
2391 if *accumulate {
2392 put(&mut out, Port::FifoRd(*to), w);
2393 }
2394 }
2395 Fixed::Matmul { block, per_site_n, sub_rows, ins, reload, .. } => {
2396 let w = ctx.matmul_run(block, ctx.page_row(ins[1], *per_site_n), *per_site_n, *sub_rows);
2397 for b in ins.iter().chain(reload) {
2398 put(&mut out, Port::FifoRd(*b), w);
2399 }
2400 }
2401 Fixed::Unary { ins, .. } => {
2402 for b in ins {
2403 put(&mut out, Port::FifoRd(*b), 1);
2404 }
2405 }
2406 Fixed::Generic { unit, ins, .. } => {
2408 for (s, b) in ins.iter().enumerate() {
2409 put(&mut out, Port::FifoRd(*b), if unit.broadcast(s) || unit.gathers(s) { 1 } else { unit.lanes });
2410 }
2411 }
2412 Fixed::McastSend { buf, .. } => put(&mut out, Port::FifoRd(*buf), 1),
2413 Fixed::Push { .. }
2414 | Fixed::Pop { .. }
2415 | Fixed::Barrier
2416 | Fixed::AcquireAcc { .. }
2417 | Fixed::ReleaseAcc
2418 | Fixed::SemSet { .. }
2419 | Fixed::SemInc { .. }
2420 | Fixed::McastSetValid { .. } => {}
2421 }
2422 }
2423 out
2424}
2425
2426fn read_ports(ctx: &Ctx, plans: &[EnginePlan], site: Site) -> (BTreeMap<(usize, BufferId), usize>, BTreeMap<BufferId, usize>) {
2430 let mut readers: BTreeMap<BufferId, usize> = BTreeMap::new();
2431 let mut index: BTreeMap<(usize, BufferId), usize> = BTreeMap::new();
2432 for (ei, plan) in plans.iter().enumerate().filter(|(_, pl)| pl.site == site) {
2433 let lanes = gather_lanes(ctx, &plan.micro);
2434 for b in buffer_uses(&plan.micro).reads {
2435 let n = readers.entry(ctx.template(b)).or_default();
2436 index.insert((ei, b), *n);
2437 *n += lanes.get(&ctx.template(b)).copied().unwrap_or(1);
2438 }
2439 }
2440 (index, readers)
2441}
2442
2443pub(super) fn gather_lanes(ctx: &Ctx, micro: &[Micro]) -> BTreeMap<BufferId, usize> {
2447 let mut out: BTreeMap<BufferId, usize> = BTreeMap::new();
2448 for m in micro {
2449 let Micro::Fixed(Fixed::Generic { unit, ins, .. }) = m else { continue };
2450 if unit.lanes > 1 {
2451 for s in (0..unit.slots()).filter(|s| unit.gathers(*s)) {
2452 let lanes = out.entry(ctx.template(ins[s])).or_insert(1);
2453 *lanes = (*lanes).max(unit.lanes);
2454 }
2455 }
2456 }
2457 out
2458}
2459
2460fn widths_or_element(found: BTreeSet<usize>) -> BTreeSet<usize> {
2462 if found.is_empty() {
2463 BTreeSet::from([1])
2464 } else {
2465 found
2466 }
2467}
2468
2469fn tensors_read(micro: &[Micro]) -> BTreeSet<TensorId> {
2470 micro
2471 .iter()
2472 .filter_map(|m| {
2473 if let Micro::Fixed(Fixed::ReadTiles { tensor, .. }) = m {
2474 Some(*tensor)
2475 } else {
2476 None
2477 }
2478 })
2479 .collect()
2480}
2481fn tensors_written(micro: &[Micro]) -> BTreeSet<TensorId> {
2482 micro
2483 .iter()
2484 .filter_map(|m| {
2485 if let Micro::Fixed(Fixed::WriteTiles { tensor, .. }) = m {
2486 Some(*tensor)
2487 } else {
2488 None
2489 }
2490 })
2491 .collect()
2492}
2493fn halts_in(micro: &[Micro]) -> bool {
2495 micro.iter().any(|m| matches!(m, Micro::Guarded(Guard::Link { .. })))
2496}
2497fn sems_local(micro: &[Micro]) -> BTreeSet<SemId> {
2498 let mut s = BTreeSet::new();
2499 for m in micro {
2500 match m {
2501 Micro::Guarded(Guard::SemWait { sem, .. })
2502 | Micro::Fixed(Fixed::SemSet { sem, .. })
2503 | Micro::Then(Fixed::SemSet { sem, .. }) => {
2504 s.insert(*sem);
2505 }
2506 _ => {}
2507 }
2508 }
2509 s
2510}
2511fn sem_incs(micro: &[Micro]) -> BTreeSet<SemId> {
2512 micro
2513 .iter()
2514 .filter_map(|m| {
2515 if let Micro::Fixed(Fixed::SemInc { sem, .. }) | Micro::Then(Fixed::SemInc { sem, .. }) = m {
2516 Some(*sem)
2517 } else {
2518 None
2519 }
2520 })
2521 .collect()
2522}
2523fn mcast_valids(micro: &[Micro]) -> BTreeSet<(SemId, GroupId)> {
2524 micro
2525 .iter()
2526 .filter_map(|m| {
2527 if let Micro::Fixed(Fixed::McastSetValid { sem, group }) | Micro::Then(Fixed::McastSetValid { sem, group }) = m {
2528 Some((*sem, *group))
2529 } else {
2530 None
2531 }
2532 })
2533 .collect()
2534}
2535fn mcast_sends(micro: &[Micro]) -> BTreeSet<GroupId> {
2536 micro
2537 .iter()
2538 .filter_map(|m| {
2539 if let Micro::Fixed(Fixed::McastSend { group, .. }) = m {
2540 Some(*group)
2541 } else {
2542 None
2543 }
2544 })
2545 .collect()
2546}
2547fn loop_vars(micro: &[Micro]) -> BTreeSet<LoopVar> {
2548 micro
2549 .iter()
2550 .filter_map(|m| {
2551 if let Micro::LoopStart { var, .. } = m {
2552 Some(*var)
2553 } else {
2554 None
2555 }
2556 })
2557 .collect()
2558}
2559
2560fn pe_acc_bits(bits: u32, k: usize) -> u32 {
2564 (2 * bits + (usize::BITS - k.max(1).saturating_sub(1).leading_zeros())).min(64)
2565}
2566
2567fn pe_array(micro: &[Micro]) -> Option<(usize, usize, u32)> {
2569 micro.iter().fold(None, |acc, m| match m {
2570 Micro::Fixed(Fixed::Matmul { operand, array, .. }) => {
2571 let bits = precision_int_bits(*operand).unwrap_or(64);
2572 Some((array.0, array.1, acc.map_or(bits, |a: (usize, usize, u32)| a.2.max(bits))))
2573 }
2574 _ => acc,
2575 })
2576}
2577
2578fn require_composition_on_one_site(p: &SpatialProgram) -> Result<(), SpatialLowerError> {
2582 if !p.sections.is_empty() && p.sites().len() > 1 {
2583 return Err(SpatialLowerError::Unsupported(
2584 "a composition across more than one site — its item coordinates are decoded per section from one site's items",
2585 ));
2586 }
2587 Ok(())
2588}
2589
2590fn index_read(ctx: &Ctx, at: &TileAddr) -> String {
2593 match at.indirect {
2594 Some(ind) => {
2595 let ki = ctx.template(ind.index).idx();
2596 format!(" f{ki}_rd_slot = f{ki}_rd_slot0; f{ki}_rd_elem = 0;")
2597 }
2598 None => String::new(),
2599 }
2600}
2601
2602struct CoordNames(String, String);
2605
2606impl CoordNames {
2607 fn as_tuple(&self) -> (&str, &str) {
2608 (&self.0, &self.1)
2609 }
2610}
2611
2612fn item_coord_names(section: Option<usize>) -> CoordNames {
2613 match section {
2614 Some(k) => CoordNames(format!("item_y_s{k}(lv5_or_zero)"), format!("item_x_s{k}(lv5_or_zero)")),
2615 None => CoordNames("oy".into(), "ox".into()),
2616 }
2617}
2618
2619fn lit64(v: i64) -> String {
2623 if v < 0 {
2624 format!("(-64'sd{})", v.unsigned_abs())
2625 } else {
2626 format!("64'sd{v}")
2627 }
2628}
2629
2630fn fold_step(pool: ReducePool, first: &str, val_reg: &str, idx_reg: &str, v: &str, at: &str) -> (String, String) {
2634 match pool {
2635 ReducePool::Sum => (format!("({first} ? {v} : ($signed({val_reg}) + {v}))"), "32'd0".into()),
2636 ReducePool::Max => (
2637 format!("({first} ? {v} : (($signed({v}) > $signed({val_reg})) ? {v} : $signed({val_reg})))"),
2638 "32'd0".into(),
2639 ),
2640 ReducePool::ArgMax => {
2641 let take = format!("({first} || ($signed({v}) > $signed({val_reg})))");
2642 (format!("({take} ? {v} : $signed({val_reg}))"), format!("({take} ? {at} : {idx_reg})"))
2643 }
2644 }
2645}
2646
2647#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2658enum Storage {
2659 Flops,
2660 Registers,
2661 Distributed,
2662 Block,
2663}
2664
2665impl Storage {
2666 fn of(words: usize, banks: usize, lat: u64) -> Storage {
2668 let rows = words.max(1).div_ceil(banks);
2669 if lat == 0 {
2670 Storage::Flops
2671 } else if rows <= 1 || (banks == 1 && rows <= 4) {
2672 Storage::Registers
2673 } else if rows <= 64 && banks > 1 {
2674 Storage::Distributed
2675 } else {
2676 Storage::Block
2677 }
2678 }
2679
2680 fn attr(self) -> &'static str {
2682 match self {
2683 Storage::Flops => "",
2684 Storage::Registers => "(* ram_style = \"registers\" *) ",
2685 Storage::Distributed => "(* ram_style = \"distributed\" *) ",
2686 Storage::Block => "(* ram_style = \"block\" *) ",
2687 }
2688 }
2689}
2690
2691fn memory_decl(name: &str, words: usize, width: u32, banks: usize, lat: u64) -> String {
2692 let rows = words.max(1).div_ceil(banks);
2693 let style = Storage::of(words, banks, lat).attr();
2694 let ls = lanes(width);
2695 let mut out = Vec::new();
2696 for b in 0..banks {
2697 for (l, (_, w)) in ls.iter().enumerate() {
2698 out.push(format!(" {style}reg [{}:0] {} [0:{}];", w - 1, lane(&bank(name, b, banks), l, ls.len()), rows - 1));
2699 }
2700 }
2701 out.join("\n")
2702}
2703
2704fn memory_read(data: &str, mem: &str, addr: &str, width: u32, banks: usize, lat: u64, runs: &BTreeSet<usize>) -> String {
2710 let ls = lanes(width);
2711 let read = |m: &str, row: &str, q: &str| -> String {
2712 let mut s = String::new();
2713 if lat == 0 {
2714 let parts: Vec<String> = (0..ls.len()).rev().map(|l| format!("{}[{row}]", lane(m, l, ls.len()))).collect();
2715 s.push_str(&format!(" wire [{}:0] {q} = {{{}}};", width - 1, parts.join(", ")));
2716 return s;
2717 }
2718 for (l, (_, w)) in ls.iter().enumerate() {
2719 s.push_str(&format!(" reg [{}:0] {q}_l{l}_q0; always @(posedge clk) {q}_l{l}_q0 <= {}[{row}];", w - 1, lane(m, l, ls.len())));
2720 for n in 1..lat {
2721 s.push_str(&format!(" reg [{0}:0] {q}_l{l}_q{n}; always @(posedge clk) {q}_l{l}_q{n} <= {q}_l{l}_q{1};", w - 1, n - 1));
2722 }
2723 }
2724 let parts: Vec<String> = (0..ls.len()).rev().map(|l| format!("{q}_l{l}_q{}", lat - 1)).collect();
2725 s.push_str(&format!(" wire [{}:0] {q} = {{{}}};", width - 1, parts.join(", ")));
2726 s
2727 };
2728 if banks == 1 {
2729 let s = format!("{} wire [63:0] {data} = $signed({data}_w);", read(mem, addr, &format!("{data}_w")));
2730 return format!(" {}", s.trim_start());
2731 }
2732 let lg = log2_banks(banks);
2733 let mut s = format!(" wire [31:0] {data}_a = {addr}; wire [31:0] {data}_r = {data}_a >> {lg}; wire [{}:0] {data}_o0 = {data}_a[{}:0];", lg - 1, lg - 1);
2734 for b in 0..banks {
2735 s.push_str(&read(&bank(mem, b, banks), &format!("{data}_r"), &format!("{data}_k{b}")));
2736 }
2737 for n in 1..=lat {
2739 s.push_str(&format!(" reg [{}:0] {data}_o{n}; always @(posedge clk) {data}_o{n} <= {data}_o{};", lg - 1, n - 1));
2740 }
2741 for j in 0..banks {
2745 let Some(step) = runs.iter().copied().filter(|w| *w > j).min() else {
2746 s.push_str(&format!(" wire [{}:0] {data}_v{j} = {}'d0;", width - 1, width));
2747 continue;
2748 };
2749 if step == banks {
2750 s.push_str(&format!(" wire [{}:0] {data}_v{j} = {data}_k{j};", width - 1));
2751 continue;
2752 }
2753 let sh = step.trailing_zeros();
2754 s.push_str(&format!(" reg [{}:0] {data}_v{j}; always @* case ({data}_o{lat}[{}:{sh}])", width - 1, lg - 1));
2755 for m in 0..banks / step {
2756 s.push_str(&format!(" {}'d{m}: {data}_v{j} = {data}_k{};", lg - sh, m * step + j));
2757 }
2758 s.push_str(" endcase");
2759 }
2760 let hi: Vec<String> = (1..banks).rev().map(|j| format!("{data}_v{j}")).collect();
2761 s.push_str(&format!(" wire [63:0] {data} = $signed({data}_v0); wire [{}:0] {data}_hi = {{{}}};", (banks as u32 - 1) * width - 1, hi.join(", ")));
2762 s
2763}
2764
2765fn delayed(name: &str, src: &str, width: usize, lat: u64) -> String {
2767 if lat == 0 {
2768 return format!(" assign {name} = {src};");
2769 }
2770 let mut s = String::new();
2771 for n in 0..lat {
2772 let from = if n == 0 { src.to_string() } else { format!("{name}_d{}", n - 1) };
2773 s.push_str(&format!(" reg [{}:0] {name}_d{n}; always @(posedge clk) {name}_d{n} <= {from};", width - 1));
2774 }
2775 s.push_str(&format!(" assign {name} = {name}_d{};", lat - 1));
2776 s
2777}
2778
2779fn ring_wrap(v: &str, depth: usize, hi: usize) -> String {
2783 if depth.is_power_of_two() {
2784 format!("{v} % {depth}")
2785 } else {
2786 let w = hi + 1;
2787 format!("({v} >= {w}'d{depth}) ? {v} - {w}'d{depth} : {v}")
2788 }
2789}
2790
2791pub fn fifo_control_verilog(name: &str, depth: usize) -> String {
2795 let (sw, cw) = (bits_for(depth), bits_for(depth + 1));
2796 let aw = bits_for(2 * depth);
2798 let mut e = Emit { s: String::new() };
2799 e.l(format!("module {name}("));
2800 e.l(" input clk, input rst,");
2801 e.l(format!(" input reserve, input [{cw}:0] reserve_n, input push, input [{cw}:0] push_n, input pop, input [{cw}:0] pop_n,"));
2802 e.l(format!(" output [{cw}:0] free, output [{cw}:0] count, output [{sw}:0] fill_slot0, output [{sw}:0] rd_slot0"));
2803 e.l(");");
2804 e.l(format!(" reg [{cw}:0] cnt, rsv; reg [{sw}:0] rd;"));
2805 e.l(format!(" wire [{aw}:0] fill_at = rd + cnt, rd_at = rd + pop_n;"));
2806 e.l(format!(" assign free = {depth} - cnt - rsv;"));
2807 e.l(" assign count = cnt;");
2808 e.l(format!(" assign fill_slot0 = {};", ring_wrap("fill_at", depth, aw)));
2809 e.l(" assign rd_slot0 = rd;");
2810 e.l(" always @(posedge clk) begin");
2811 e.l(" if (rst) begin cnt <= 0; rsv <= 0; rd <= 0; end");
2812 e.l(" else begin");
2813 e.l(" cnt <= cnt + (push ? push_n : 0) - (pop ? pop_n : 0);");
2814 e.l(" rsv <= rsv + (reserve ? reserve_n : 0) - (push ? push_n : 0);");
2815 e.l(format!(" if (pop) rd <= {};", ring_wrap("rd_at", depth, aw)));
2816 e.l(" end");
2817 e.l(" end");
2818 e.l("endmodule");
2819 e.s
2820}
2821
2822#[allow(clippy::too_many_arguments)]
2827fn print_pool(name: &str, words: usize, width: u32, banks: usize, lat: u64, nread: usize, wr: &BTreeSet<usize>, rd: &[BTreeSet<usize>]) -> String {
2828 let lg = log2_banks(banks);
2829 let hi = (banks as u32 - 1) * width;
2830 let mut e = Emit { s: String::new() };
2831 let mut ports = vec!["input clk".to_string(), "input rst".into(), "input wr_en".into(), "input [31:0] wr_addr".into(), "input [63:0] wr_data".into()];
2832 if banks > 1 {
2833 ports.push(format!("input [{lg}:0] wr_n"));
2834 ports.push("input [3:0] wr_lw".into());
2835 ports.push(format!("input [{}:0] wr_hi", hi - 1));
2836 }
2837 for r in 0..nread {
2838 ports.push(format!("input [31:0] rd{r}_addr"));
2839 ports.push(format!("output [63:0] rd{r}_data"));
2840 if banks > 1 {
2841 ports.push(format!("output [{}:0] rd{r}_hi", hi - 1));
2842 }
2843 }
2844 e.l(format!("module {name}({});", ports.join(", ")));
2845 e.l(memory_decl("mem", words, width, banks, lat));
2846 for (r, runs) in rd.iter().enumerate() {
2847 e.l(memory_read(&format!("rd{r}_word"), "mem", &format!("rd{r}_addr"), width, banks, lat, runs));
2848 e.l(format!(" assign rd{r}_data = rd{r}_word;"));
2849 if banks > 1 {
2850 e.l(format!(" assign rd{r}_hi = rd{r}_word_hi;"));
2851 }
2852 }
2853 let (wires, put) = memory_write("wm", "mem", "wr_addr", "wr_n", "wr_lw", "wr_data", "wr_hi", width, banks, wr);
2854 if !wires.is_empty() {
2855 e.l(wires);
2856 }
2857 e.l(format!(" always @(posedge clk) if (wr_en && !rst) begin {put} end"));
2858 e.l("endmodule");
2859 e.s
2860}
2861
2862struct Pool {
2865 members: Vec<BufferId>,
2866 words: usize,
2867 width: u32,
2868 nread: usize,
2869 wr: BTreeSet<usize>,
2870 rd: Vec<BTreeSet<usize>>,
2871}
2872
2873fn page_stride(ctx: &Ctx, b: BufferId) -> usize {
2875 ctx.page_words(b).next_multiple_of(ctx.banks)
2876}
2877
2878fn site_pools(ctx: &Ctx, plans: &[EnginePlan], site: Site) -> Vec<Pool> {
2890 let p = ctx.p;
2891 let phases = p.section_phases();
2892 let (rd_index, readers) = read_ports(ctx, plans, site);
2893 let widths: BTreeMap<usize, BTreeMap<Port, BTreeSet<usize>>> =
2894 plans.iter().enumerate().filter(|(_, pl)| pl.site == site).map(|(ei, pl)| (ei, port_widths(ctx, &pl.micro))).collect();
2895 let bus_fed: BTreeSet<BufferId> = p
2896 .stages
2897 .iter()
2898 .filter(|st| st.site == Some(site) && matches!(st.kind, StageKind::Recv { .. }))
2899 .flat_map(|st| st.outs.iter().copied())
2900 .collect();
2901 let nread = |b: BufferId| readers.get(&ctx.template(b)).copied().unwrap_or(1).max(1);
2902 let mut by_ports: BTreeMap<usize, BTreeMap<Option<usize>, Vec<BufferId>>> = BTreeMap::new();
2903 for b in p.buffers.iter().filter(|b| b.site == Some(site) && !matches!(b.kind, BufferKind::Accumulator { .. })) {
2904 by_ports.entry(nread(b.id)).or_default().entry(p.section_of_buffer(b.id).map(|k| phases[k])).or_default().push(b.id);
2905 }
2906 let words = |b: BufferId| ctx.depth(b) * page_stride(ctx, b);
2907 let mut pools: Vec<Pool> = Vec::new();
2908 for (ports, by_phase) in &mut by_ports {
2909 let first = pools.len();
2910 for members in by_phase.values_mut() {
2911 members.sort_by_key(|b| (std::cmp::Reverse(words(*b) * ctx.buffer_width(*b) as usize), b.idx()));
2912 for (i, b) in members.iter().enumerate() {
2913 if pools.len() == first + i {
2914 pools.push(Pool { members: Vec::new(), words: 0, width: 1, nread: *ports, wr: BTreeSet::new(), rd: Vec::new() });
2915 }
2916 let pool = &mut pools[first + i];
2917 pool.members.push(*b);
2918 pool.words = pool.words.max(words(*b));
2919 pool.width = pool.width.max(ctx.buffer_width(*b));
2920 for found in widths.values().filter_map(|w| w.get(&Port::FifoWr(*b))) {
2921 pool.wr.extend(found);
2922 }
2923 if bus_fed.contains(b) {
2924 pool.wr.insert(1);
2925 }
2926 }
2927 }
2928 }
2929 for pool in &mut pools {
2930 pool.rd = vec![BTreeSet::new(); pool.nread];
2931 for ((ei, b), r) in &rd_index {
2932 if pool.members.contains(b) {
2933 if let Some(found) = widths.get(ei).and_then(|w| w.get(&Port::FifoRd(*b))) {
2934 pool.rd[*r].extend(found);
2935 }
2936 }
2937 }
2938 pool.wr = widths_or_element(std::mem::take(&mut pool.wr));
2939 pool.rd = pool.rd.drain(..).map(widths_or_element).collect();
2940 }
2941 pools
2942}
2943
2944impl SpatialBackend for FabricBackend {
2945 fn name(&self) -> &str {
2946 self.target.name
2947 }
2948 fn target(&self) -> &SpatialTarget {
2949 &self.target
2950 }
2951 fn lower(&self, p: &SpatialProgram) -> Result<SpatialArtifact, SpatialLowerError> {
2952 require_verified(p)?;
2953 require_composition_on_one_site(p)?;
2954 let ctx = context(p, &self.target)?;
2955 let plans = plans(&ctx)?;
2956 let pl = p.placement.as_ref().unwrap();
2957 if p.semaphores.iter().any(|s| s.init != 0) {
2958 return Err(SpatialLowerError::Unsupported(
2959 "a semaphore whose initial value is not the counter's reset value",
2960 ));
2961 }
2962 if pl
2964 .items_of_site
2965 .iter()
2966 .any(|(_, items)| items.windows(2).any(|w| w[1].idx() != w[0].idx() + 1))
2967 {
2968 return Err(SpatialLowerError::Unsupported(
2969 "a strided item enumeration on the fabric",
2970 ));
2971 }
2972 for t in &p.tensors {
2973 if precision_int_bits(t.dtype).is_none() {
2974 return Err(SpatialLowerError::Precision { needed: t.dtype });
2975 }
2976 }
2977 budget::check_capacity(p, &self.target)?;
2980 let mut rtl = Emit { s: String::new() };
2981 rtl.l(format!("// spatial_top — {} lowered to our fabric from the spatial IR; every module is one IR object.", p.name));
2982 rtl.l("`timescale 1ns/1ps");
2983 rtl.s.push_str(&emit_semaphore_verilog("sem_prim", 16));
2985 if p.semaphores.iter().any(|s| matches!(s.role, SemRole::Barrier { .. })) {
2988 rtl.l("// barrier_prim — a section rendezvous: the counting semaphore every engine program arrives at between two sections.");
2989 rtl.l("module barrier_prim(input clk, input rst, input inc, input [15:0] inc_n, input set, input [15:0] set_val, input [15:0] thresh, output [15:0] count, output ge);");
2990 rtl.l(" sem_prim u(.clk(clk), .rst(rst), .inc(inc), .inc_n(inc_n), .set(set), .set_val(set_val), .thresh(thresh), .count(count), .ge(ge));");
2991 rtl.l("endmodule");
2992 }
2993 let mut bus_widths: BTreeSet<usize> = BTreeSet::new();
2994 for g in &pl.groups {
2995 bus_widths.insert(g.rect.len() - 1);
2996 }
2997 for n in &bus_widths {
2998 rtl.s.push_str(&emit_mcast_bus_verilog(
2999 &format!("mcast_bus_prim_{n}"),
3000 80,
3001 *n,
3002 ));
3003 }
3004 let mut templates: BTreeMap<BufferId, usize> = BTreeMap::new();
3006 for b in &p.buffers {
3007 if matches!(b.kind, BufferKind::Accumulator { .. }) {
3008 continue;
3009 }
3010 templates.entry(ctx.template(b.id)).or_insert(ctx.depth(b.id));
3011 }
3012 for (t, depth) in &templates {
3013 rtl.s.push_str(&fifo_control_verilog(&format!("fifo_tpl_{}", t.idx()), *depth));
3014 }
3015 let mut class_rep: BTreeMap<usize, usize> = BTreeMap::new();
3017 for (i, plan) in plans.iter().enumerate() {
3018 class_rep.entry(plan.class).or_insert(i);
3019 }
3020 let mut grid_names: BTreeMap<usize, String> = BTreeMap::new();
3021 for (class, rep) in &class_rep {
3022 let plan = &plans[*rep];
3023 for m in &plan.micro {
3025 if let Micro::Fixed(Fixed::Unary {
3026 kind: UnaryKind::Scalar(body),
3027 ..
3028 }) = m
3029 {
3030 let g = route_loop_body(&body.cells, body.n_in, &[body.result]);
3031 let gname = format!("c{class}");
3032 rtl.s.push_str(&emit_grid_verilog(&gname, &g));
3033 grid_names.insert(*class, gname);
3034 break;
3035 }
3036 }
3037 if let Some((k, n, bits)) = pe_array(&plan.micro) {
3038 rtl.s.push_str(&emit_systolic_pipelined_loadable_verilog_starting(
3039 &format!("pe_engine_{class}"),
3040 k,
3041 n,
3042 bits,
3043 pe_acc_bits(bits, k),
3044 WeightStart::Zero,
3045 ));
3046 }
3047 let class_plans: Vec<&EnginePlan> = plans.iter().filter(|pl| pl.class == *class).collect();
3048 rtl.s.push_str(&engine::print_engine(
3049 &ctx,
3050 plan,
3051 &class_plans,
3052 &format!("engine_{class}"),
3053 &grid_names,
3054 )?);
3055 }
3056 let sites: Vec<Site> = pl.items_of_site.iter().map(|(s, _)| *s).collect();
3058 let mut hex: Vec<(String, String)> = Vec::new();
3059 for t in &p.tensors {
3060 if t.role == TensorRole::Output {
3061 continue;
3062 }
3063 let cols = t.elems.cols;
3064 let words = match &t.init {
3065 TensorInit::Values(v) => {
3066 tiled_words(p, t, &|i, j| v.get(i * cols + j).copied().unwrap_or(0))
3067 }
3068 TensorInit::Pattern { .. } => {
3069 tiled_words(p, t, &|i, j| t.init.element(i * cols + j).unwrap_or(0))
3070 }
3071 _ => vec![0i64; ctx.tensor_words(t)],
3074 };
3075 hex.extend(tensor_hex(t, &words, tensor_width(t, &ctx.ranges), ctx.banks));
3076 }
3077 for site in &sites {
3078 rtl.s.push_str(&print_site(&ctx, &plans, *site)?);
3079 }
3080 rtl.s.push_str(&print_top(&ctx, &plans, &sites)?);
3082 let census = Census::of(p);
3083 let cycle_model = fabric_cycle_model(p, &self.target);
3084 let floor = cycle_floor(&ctx, &plans);
3085 let testbench = print_testbench(&ctx, &sites, cycle_model);
3086 Ok(SpatialArtifact::Verilog(VerilogArtifact {
3087 top: "spatial_top".into(),
3088 rtl: rtl.s,
3089 testbench,
3090 hex,
3091 census,
3092 cycle_model,
3093 cycle_floor: floor,
3094 }))
3095 }
3096}
3097
3098fn print_site(ctx: &Ctx, plans: &[EnginePlan], site: Site) -> Result<String, SpatialLowerError> {
3101 let p = ctx.p;
3102 let pl = p.placement.as_ref().unwrap();
3103 let mine: Vec<(usize, &EnginePlan)> = plans
3104 .iter()
3105 .enumerate()
3106 .filter(|(_, pl)| pl.site == site)
3107 .collect();
3108 let bufs: Vec<&Buffer> = p
3109 .buffers
3110 .iter()
3111 .filter(|b| b.site == Some(site) && !matches!(b.kind, BufferKind::Accumulator { .. }))
3112 .collect();
3113 let (rd_index, readers) = read_ports(ctx, plans, site);
3115 let widths: BTreeMap<usize, BTreeMap<Port, BTreeSet<usize>>> = mine.iter().map(|(ei, pl)| (*ei, port_widths(ctx, &pl.micro))).collect();
3116 let tensor_widths = |ei: usize, port: Port| -> BTreeSet<usize> { widths.get(&ei).and_then(|w| w.get(&port)).cloned().unwrap_or_default() };
3117 let sems: Vec<&Semaphore> = p
3118 .semaphores
3119 .iter()
3120 .filter(|s| s.sites.contains(&site))
3121 .collect();
3122 let mut bus_fed: BTreeMap<BufferId, GroupId> = BTreeMap::new();
3124 for st in &p.stages {
3125 if st.site == Some(site) {
3126 if let StageKind::Recv { group } = st.kind {
3127 for b in &st.outs {
3128 bus_fed.insert(*b, group);
3129 }
3130 }
3131 }
3132 }
3133 let mut inc_sources: BTreeMap<SemId, Vec<usize>> = BTreeMap::new();
3135 let mut valid_sources: BTreeMap<SemId, Vec<(usize, GroupId)>> = BTreeMap::new();
3136 for (ei, plan) in plans.iter().enumerate() {
3137 for m in &plan.micro {
3138 match m {
3139 Micro::Fixed(Fixed::SemInc { sem, at, .. }) | Micro::Then(Fixed::SemInc { sem, at, .. }) if *at == site => {
3140 inc_sources.entry(*sem).or_default().push(ei)
3141 }
3142 Micro::Fixed(Fixed::McastSetValid { sem, group }) | Micro::Then(Fixed::McastSetValid { sem, group }) => {
3143 let g = &pl.groups[group.idx()];
3144 if g.rect.sites().contains(&site) && g.sender != site {
3145 valid_sources.entry(*sem).or_default().push((ei, *group));
3146 }
3147 }
3148 _ => {}
3149 }
3150 }
3151 }
3152 let mut e = Emit { s: String::new() };
3153 let mut ports = vec![
3154 "input clk".to_string(),
3155 "input rst".to_string(),
3156 "input start".to_string(),
3157 "output done".to_string(),
3158 "input host_we".to_string(),
3159 "input host_re".to_string(),
3160 "input [15:0] host_tensor".to_string(),
3161 "input [31:0] host_addr".to_string(),
3162 "input [63:0] host_wdata".to_string(),
3163 "output [63:0] host_rdata".to_string(),
3164 ];
3165 for (sem, srcs) in &inc_sources {
3166 for ei in srcs {
3167 ports.push(format!("input inc_s{}_from_e{ei}", sem.idx()));
3168 ports.push(format!("input [15:0] inc_s{}_n_from_e{ei}", sem.idx()));
3169 }
3170 }
3171 for (sem, srcs) in &valid_sources {
3172 for (ei, g) in srcs {
3173 ports.push(format!(
3174 "input valid_s{}_from_e{ei}_g{}",
3175 sem.idx(),
3176 g.idx()
3177 ));
3178 }
3179 }
3180 for (ei, plan) in &mine {
3182 for sem in sem_incs(&plan.micro) {
3183 ports.push(format!("output e{ei}_s{}_inc_remote", sem.idx()));
3184 ports.push(format!("output [15:0] e{ei}_s{}_inc_n", sem.idx()));
3185 }
3186 for (sem, g) in mcast_valids(&plan.micro) {
3187 ports.push(format!("output e{ei}_mv{}_g{}", sem.idx(), g.idx()));
3188 }
3189 for g in mcast_sends(&plan.micro) {
3190 ports.push(format!("output e{ei}_mc{}_send", g.idx()));
3191 ports.push(format!("output [79:0] e{ei}_mc{}_din", g.idx()));
3192 }
3193 }
3194 for (b, g) in &bus_fed {
3195 ports.push(format!("input bus_g{}_push_b{}", g.idx(), b.idx()));
3196 ports.push(format!("input [79:0] bus_g{}_dout_b{}", g.idx(), b.idx()));
3197 }
3198 let halting: Vec<usize> = mine.iter().filter(|(_, pl)| halts_in(&pl.micro)).map(|(ei, _)| *ei).collect();
3199 if !halting.is_empty() {
3200 ports.push("input [7:0] link_n".to_string());
3201 ports.push("output halted".to_string());
3202 ports.push("output [15:0] halt_at".to_string());
3203 }
3204 e.l(format!("module site_{}_{}(", site.x, site.y));
3205 e.l(format!(" {}", ports.join(",\n ")));
3206 e.l(");");
3207 let banks = ctx.banks;
3209 let hi_of = |width: u32| (banks as u32 - 1) * width;
3210 for t in &p.tensors {
3211 let id = t.id.idx();
3212 let words = ctx.tensor_words(t);
3213 let rows = words.max(1).div_ceil(banks);
3214 let width = tensor_width(t, &ctx.ranges);
3215 let n = lanes(width).len();
3216 e.l(memory_decl(&format!("mem_t{id}"), words, width, banks, ctx.lat));
3217 for b in 0..banks {
3218 for l in 0..n {
3219 let mem = lane(&bank(&format!("mem_t{id}"), b, banks), l, n);
3220 if t.role != TensorRole::Output {
3221 let stem = bank(&format!("t{id}"), b, banks);
3222 let file = if n == 1 { format!("{stem}.hex") } else { format!("{stem}_l{l}.hex") };
3223 e.l(format!(" initial $readmemh(\"{file}\", {mem});"));
3224 } else {
3225 let zi = format!("zi{id}_{}{l}", if banks == 1 { String::new() } else { format!("{b}_") });
3226 e.l(format!(" integer {zi}; initial for ({zi} = 0; {zi} < {rows}; {zi} = {zi} + 1) {mem}[{zi}] = 0;"));
3227 }
3228 }
3229 }
3230 }
3231 for b in &bufs {
3233 let k = b.id.idx();
3234 let (sw, ew, cw) = (
3235 bits_for(ctx.depth(b.id)),
3236 bits_for(ctx.page_words(b.id)),
3237 bits_for(ctx.depth(b.id) + 1),
3238 );
3239 let nrd = readers
3240 .get(&ctx.template(b.id))
3241 .copied()
3242 .unwrap_or(1)
3243 .max(1);
3244 e.l(format!(" wire f{k}_reserve, f{k}_push, f{k}_pop, f{k}_wr_en; wire [{cw}:0] f{k}_reserve_n, f{k}_push_n, f{k}_pop_n, f{k}_free, f{k}_count; wire [{sw}:0] f{k}_fill_slot0, f{k}_rd_slot0, f{k}_wr_slot; wire [{ew}:0] f{k}_wr_elem; wire [63:0] f{k}_wr_data;"));
3245 let bw = ctx.buffer_width(b.id);
3246 if banks > 1 {
3247 e.l(format!(" wire [{}:0] f{k}_wr_n; wire [3:0] f{k}_wr_lw; wire [{}:0] f{k}_wr_hi;", log2_banks(banks), hi_of(bw) - 1));
3248 }
3249 for r in 0..nrd {
3250 e.l(format!(" wire [{sw}:0] f{k}_rd{r}_slot; wire [{ew}:0] f{k}_rd{r}_elem; wire [63:0] f{k}_rd{r}_data;"));
3251 if banks > 1 {
3252 e.l(format!(" wire [{}:0] f{k}_rd{r}_hi;", hi_of(bw) - 1));
3253 }
3254 }
3255 e.l(format!(
3256 " fifo_tpl_{} fifo_{}_{}_{}(.clk(clk), .rst(rst), .reserve(f{k}_reserve), .reserve_n(f{k}_reserve_n), .push(f{k}_push), .push_n(f{k}_push_n), .pop(f{k}_pop), .pop_n(f{k}_pop_n), .free(f{k}_free), .count(f{k}_count), .fill_slot0(f{k}_fill_slot0), .rd_slot0(f{k}_rd_slot0));",
3257 ctx.template(b.id).idx(),
3258 ident(&b.name),
3259 site.x,
3260 site.y,
3261 ));
3262 }
3263 let pools = site_pools(ctx, plans, site);
3266 for (i, pool) in pools.iter().enumerate() {
3267 let pn = format!("pool{i}");
3268 let gated = |en: &str, v: &str, zero: &str| -> String { format!("({en} ? {v} : {zero})") };
3269 let addr = |b: BufferId, slot: &str, elem: &str| format!("(f{0}_{slot} * {1} + f{0}_{elem})", b.idx(), page_stride(ctx, b));
3270 let hi_w = hi_of(pool.width);
3271 e.l(format!(" wire {pn}_we = {};", pool.members.iter().map(|b| format!("f{}_wr_en", b.idx())).collect::<Vec<_>>().join(" | ")));
3272 e.l(format!(
3273 " wire [31:0] {pn}_wa = {};",
3274 pool.members.iter().map(|b| gated(&format!("f{}_wr_en", b.idx()), &addr(*b, "wr_slot", "wr_elem"), "32'd0")).collect::<Vec<_>>().join(" | ")
3275 ));
3276 e.l(format!(
3277 " wire [63:0] {pn}_wd = {};",
3278 pool.members.iter().map(|b| gated(&format!("f{}_wr_en", b.idx()), &format!("f{}_wr_data", b.idx()), "64'd0")).collect::<Vec<_>>().join(" | ")
3279 ));
3280 let mut conn = format!(".clk(clk), .rst(rst), .wr_en({pn}_we), .wr_addr({pn}_wa), .wr_data({pn}_wd)");
3281 if banks > 1 {
3282 let lgb = log2_banks(banks);
3283 e.l(format!(
3284 " wire [{lgb}:0] {pn}_wn = {}; wire [3:0] {pn}_wl = {};",
3285 pool.members.iter().map(|b| gated(&format!("f{}_wr_en", b.idx()), &format!("f{}_wr_n", b.idx()), &format!("{}'d0", lgb + 1))).collect::<Vec<_>>().join(" | "),
3286 pool.members.iter().map(|b| gated(&format!("f{}_wr_en", b.idx()), &format!("f{}_wr_lw", b.idx()), "4'd0")).collect::<Vec<_>>().join(" | ")
3287 ));
3288 e.l(format!(
3289 " wire [{}:0] {pn}_wh = {};",
3290 hi_w - 1,
3291 pool.members
3292 .iter()
3293 .map(|b| gated(&format!("f{}_wr_en", b.idx()), &resize_lanes(&format!("f{}_wr_hi", b.idx()), ctx.buffer_width(*b), pool.width, banks - 1), &format!("{hi_w}'d0")))
3294 .collect::<Vec<_>>()
3295 .join(" | ")
3296 ));
3297 conn.push_str(&format!(", .wr_n({pn}_wn), .wr_lw({pn}_wl), .wr_hi({pn}_wh)"));
3298 }
3299 if pool.members.len() > 1 {
3300 e.l(" // synthesis translate_off");
3301 e.l(format!(
3302 " always @(posedge clk) if ({} > 1) begin $display(\"FATAL: {pn}: two of its buffers write in one cycle\"); $finish; end",
3303 pool.members.iter().map(|b| format!("f{}_wr_en", b.idx())).collect::<Vec<_>>().join(" + ")
3304 ));
3305 e.l(" // synthesis translate_on");
3306 }
3307 for r in 0..pool.nread {
3308 let on: Vec<BufferId> = pool.members.iter().copied().filter(|b| r < readers.get(&ctx.template(*b)).copied().unwrap_or(1).max(1)).collect();
3309 let ra = if on.is_empty() {
3310 "32'd0".to_string()
3311 } else {
3312 on.iter().map(|b| addr(*b, &format!("rd{r}_slot"), &format!("rd{r}_elem"))).collect::<Vec<_>>().join(" | ")
3313 };
3314 e.l(format!(" wire [31:0] {pn}_ra{r} = {ra}; wire [63:0] {pn}_rd{r};"));
3315 conn.push_str(&format!(", .rd{r}_addr({pn}_ra{r}), .rd{r}_data({pn}_rd{r})"));
3316 if banks > 1 {
3317 e.l(format!(" wire [{}:0] {pn}_rh{r};", hi_w - 1));
3318 conn.push_str(&format!(", .rd{r}_hi({pn}_rh{r})"));
3319 }
3320 for b in &on {
3321 e.l(format!(" assign f{0}_rd{r}_data = {pn}_rd{r};", b.idx()));
3322 if banks > 1 {
3323 e.l(format!(" assign f{0}_rd{r}_hi = {1};", b.idx(), resize_lanes(&format!("{pn}_rh{r}"), pool.width, ctx.buffer_width(*b), banks - 1)));
3324 }
3325 }
3326 }
3327 e.l(format!(" pool_{}_{}_{i} {pn}_{}_{}({conn});", site.x, site.y, site.x, site.y));
3328 }
3329 for s in &sems {
3331 let k = s.idx_str();
3332 let incs = inc_sources.get(&s.id).cloned().unwrap_or_default();
3333 let valids = valid_sources.get(&s.id).cloned().unwrap_or_default();
3334 let inc_any = if incs.is_empty() {
3335 "1'b0".to_string()
3336 } else {
3337 incs.iter()
3338 .map(|ei| format!("inc_s{k}_from_e{ei}"))
3339 .collect::<Vec<_>>()
3340 .join(" | ")
3341 };
3342 let inc_sum = if incs.is_empty() {
3343 "16'd0".to_string()
3344 } else {
3345 incs.iter()
3346 .map(|ei| format!("(inc_s{k}_from_e{ei} ? inc_s{k}_n_from_e{ei} : 16'd0)"))
3347 .collect::<Vec<_>>()
3348 .join(" + ")
3349 };
3350 let local: Vec<usize> = mine
3351 .iter()
3352 .filter(|(_, pl)| sems_local(&pl.micro).contains(&s.id))
3353 .map(|(ei, _)| *ei)
3354 .collect();
3355 let local_set = if local.is_empty() {
3356 "1'b0".to_string()
3357 } else {
3358 local
3359 .iter()
3360 .map(|ei| format!("e{ei}_s{k}_set"))
3361 .collect::<Vec<_>>()
3362 .join(" | ")
3363 };
3364 let local_val = local
3365 .iter()
3366 .map(|ei| format!("e{ei}_s{k}_set ? e{ei}_s{k}_set_val :"))
3367 .collect::<Vec<_>>()
3368 .join(" ");
3369 let valid_any = if valids.is_empty() {
3370 "1'b0".to_string()
3371 } else {
3372 valids
3373 .iter()
3374 .map(|(ei, g)| format!("valid_s{k}_from_e{ei}_g{}", g.idx()))
3375 .collect::<Vec<_>>()
3376 .join(" | ")
3377 };
3378 for ei in &local {
3379 e.l(format!(
3380 " wire e{ei}_s{k}_set; wire [15:0] e{ei}_s{k}_set_val;"
3381 ));
3382 }
3383 e.l(format!(" wire [15:0] s{k}_count; wire s{k}_set_any = ({local_set}) | ({valid_any}); wire [15:0] s{k}_set_v = {local_val} 16'd1;"));
3384 let prim = if matches!(s.role, SemRole::Barrier { .. }) { "barrier_prim" } else { "sem_prim" };
3385 e.l(format!(" {prim} sem_{k}_{}_{}(.clk(clk), .rst(rst), .inc({inc_any}), .inc_n({inc_sum}), .set(s{k}_set_any), .set_val(s{k}_set_v), .thresh(16'd0), .count(s{k}_count), .ge());", site.x, site.y));
3386 let _ = s.init; }
3388 let mut dones = Vec::new();
3390 for (ei, plan) in &mine {
3391 let uses = buffer_uses(&plan.micro);
3392 let gathered = gather_lanes(ctx, &plan.micro);
3393 let bufs_used: BTreeSet<BufferId> = uses.all();
3394 let mut conn = vec![
3395 ".clk(clk)".to_string(),
3396 ".rst(rst)".to_string(),
3397 ".start(start)".to_string(),
3398 format!(".done(e{ei}_done)"),
3399 ];
3400 e.l(format!(" wire e{ei}_done;"));
3401 dones.push(format!("e{ei}_done"));
3402 for b in &bufs_used {
3403 let (k, w) = (ctx.template(*b).idx(), b.idx());
3406 if uses.produces.contains(b) {
3407 for n in ["reserve", "reserve_n", "push", "push_n", "free"] {
3408 conn.push(format!(".f{k}_{n}(f{w}_{n})"));
3409 }
3410 }
3411 if uses.consumes.contains(b) {
3412 for n in ["pop", "pop_n", "count"] {
3413 conn.push(format!(".f{k}_{n}(f{w}_{n})"));
3414 }
3415 }
3416 if uses.writes.contains(b) {
3417 for n in ["wr_en", "wr_slot", "wr_elem", "wr_data", "fill_slot0"] {
3418 conn.push(format!(".f{k}_{n}(f{w}_{n})"));
3419 }
3420 if banks > 1 {
3421 conn.push(format!(".f{k}_wr_n(f{w}_wr_n), .f{k}_wr_lw(f{w}_wr_lw), .f{k}_wr_hi(f{w}_wr_hi)"));
3422 }
3423 }
3424 if uses.reads.contains(b) {
3425 let r = rd_index.get(&(*ei, *b)).copied().unwrap_or(0);
3426 for n in ["slot", "elem", "data"] {
3427 conn.push(format!(".f{k}_rd_{n}(f{w}_rd{r}_{n})"));
3428 }
3429 for l in 1..gathered.get(&ctx.template(*b)).copied().unwrap_or(1) {
3430 for n in ["slot", "elem", "data"] {
3431 conn.push(format!(".f{k}_l{l}_rd_{n}(f{w}_rd{}_{n})", r + l));
3432 }
3433 }
3434 if banks > 1 {
3435 conn.push(format!(".f{k}_rd_hi(f{w}_rd{r}_hi)"));
3436 }
3437 conn.push(format!(".f{k}_rd_slot0(f{w}_rd_slot0)"));
3438 conn.push(format!(".f{k}_fill_slot0_r(f{w}_fill_slot0)"));
3439 }
3440 }
3441 let twidth = |t: TensorId| tensor_width(p.tensor(t), &ctx.ranges);
3442 for t in tensors_read(&plan.micro) {
3443 e.l(format!(" wire [31:0] e{ei}_t{0}_rd_addr; wire [63:0] e{ei}_t{0}_rd_data;", t.idx()));
3444 conn.push(format!(".t{0}_rd_addr(e{ei}_t{0}_rd_addr)", t.idx()));
3445 conn.push(format!(".t{0}_rd_data(e{ei}_t{0}_rd_data)", t.idx()));
3446 if banks > 1 {
3447 e.l(format!(" wire [{}:0] e{ei}_t{}_rd_hi;", hi_of(twidth(t)) - 1, t.idx()));
3448 conn.push(format!(".t{0}_rd_hi(e{ei}_t{0}_rd_hi)", t.idx()));
3449 }
3450 }
3451 for t in tensors_written(&plan.micro) {
3452 e.l(format!(" wire e{ei}_t{0}_wr_en; wire [31:0] e{ei}_t{0}_wr_addr; wire [63:0] e{ei}_t{0}_wr_data;", t.idx()));
3453 conn.push(format!(".t{0}_wr_en(e{ei}_t{0}_wr_en)", t.idx()));
3454 conn.push(format!(".t{0}_wr_addr(e{ei}_t{0}_wr_addr)", t.idx()));
3455 conn.push(format!(".t{0}_wr_data(e{ei}_t{0}_wr_data)", t.idx()));
3456 if banks > 1 {
3457 e.l(format!(" wire [{0}:0] e{ei}_t{1}_wr_n; wire [3:0] e{ei}_t{1}_wr_lw; wire [{2}:0] e{ei}_t{1}_wr_hi;", log2_banks(banks), t.idx(), hi_of(twidth(t)) - 1));
3458 conn.push(format!(".t{0}_wr_n(e{ei}_t{0}_wr_n), .t{0}_wr_lw(e{ei}_t{0}_wr_lw), .t{0}_wr_hi(e{ei}_t{0}_wr_hi)", t.idx()));
3459 }
3460 }
3461 for s in sems_local(&plan.micro) {
3462 let k = s.idx();
3463 conn.push(format!(".s{k}_count(s{k}_count)"));
3464 conn.push(format!(".s{k}_set(e{ei}_s{k}_set)"));
3465 conn.push(format!(".s{k}_set_val(e{ei}_s{k}_set_val)"));
3466 }
3467 for s in sem_incs(&plan.micro) {
3468 conn.push(format!(".s{0}_inc_remote(e{ei}_s{0}_inc_remote)", s.idx()));
3469 conn.push(format!(".s{0}_inc_n(e{ei}_s{0}_inc_n)", s.idx()));
3470 }
3471 for (s, g) in mcast_valids(&plan.micro) {
3472 conn.push(format!(".mv{0}_g{1}(e{ei}_mv{0}_g{1})", s.idx(), g.idx()));
3473 }
3474 for g in mcast_sends(&plan.micro) {
3475 conn.push(format!(".mc{0}_send(e{ei}_mc{0}_send)", g.idx()));
3476 conn.push(format!(".mc{0}_din(e{ei}_mc{0}_din)", g.idx()));
3477 }
3478 if halting.contains(ei) {
3479 e.l(format!(" wire e{ei}_halted; wire [15:0] e{ei}_halt_at;"));
3480 conn.push(format!(".link_n(link_n), .halted(e{ei}_halted), .halt_at(e{ei}_halt_at)"));
3481 }
3482 e.l(format!(
3483 " engine_{} #(.ITEM_START({}), .ITEM_COUNT({})) engine_inst_{ei}({});",
3484 plan.class,
3485 plan.item_start,
3486 plan.item_count,
3487 conn.join(", ")
3488 ));
3489 }
3490 if !halting.is_empty() {
3491 e.l(format!(" assign halted = {};", halting.iter().map(|ei| format!("e{ei}_halted")).collect::<Vec<_>>().join(" | ")));
3492 let at: String = halting.iter().map(|ei| format!("e{ei}_halted ? e{ei}_halt_at : ")).collect();
3493 e.l(format!(" assign halt_at = {at}16'd0;"));
3494 }
3495 let mut host_words: Vec<String> = Vec::new();
3501 for t in &p.tensors {
3502 let id = t.id.idx();
3503 let writers: Vec<usize> = mine.iter().filter(|(_, pl)| tensors_written(&pl.micro).contains(&t.id)).map(|(ei, _)| *ei).collect();
3504 let readers: Vec<usize> = mine.iter().filter(|(_, pl)| tensors_read(&pl.micro).contains(&t.id)).map(|(ei, _)| *ei).collect();
3505 let width = tensor_width(t, &ctx.ranges);
3506 let mem = format!("mem_t{id}");
3507 e.l(format!(" wire t{id}_hw = host_we && host_tensor == 16'd{id}; wire t{id}_hr = host_re && host_tensor == 16'd{id};"));
3508 let zero_hi = format!("{}'d0", hi_of(width).max(1));
3510 let put = |e: &mut Emit, tag: &str, en: &str, addr: &str, n: &str, lw: &str, data: &str, hi: &str, runs: &BTreeSet<usize>| {
3511 let (wires, stmts) = memory_write(tag, &mem, addr, n, lw, data, hi, width, banks, runs);
3512 if !wires.is_empty() {
3513 e.l(wires);
3514 }
3515 e.l(format!(" always @(posedge clk) if ({en}) begin {stmts} end"));
3516 };
3517 match writers.split_first() {
3518 Some((w0, more)) => {
3519 e.l(format!(" wire [31:0] t{id}_wa = t{id}_hw ? host_addr : e{w0}_t{id}_wr_addr; wire [63:0] t{id}_wd = t{id}_hw ? host_wdata : e{w0}_t{id}_wr_data;"));
3520 let (n, lw, hi) = if banks > 1 {
3521 e.l(format!(
3522 " wire [{0}:0] t{id}_wn = t{id}_hw ? {0}'d1 : e{w0}_t{id}_wr_n; wire [3:0] t{id}_wl = t{id}_hw ? 4'd0 : e{w0}_t{id}_wr_lw; wire [{1}:0] t{id}_wh = t{id}_hw ? {zero_hi} : e{w0}_t{id}_wr_hi;",
3523 log2_banks(banks),
3524 hi_of(width) - 1
3525 ));
3526 (format!("t{id}_wn"), format!("t{id}_wl"), format!("t{id}_wh"))
3527 } else {
3528 (String::new(), String::new(), String::new())
3529 };
3530 let mut runs = tensor_widths(*w0, Port::TensorWr(t.id));
3532 runs.insert(1);
3533 put(&mut e, &format!("t{id}_w0"), &format!("t{id}_hw || e{w0}_t{id}_wr_en"), &format!("t{id}_wa"), &n, &lw, &format!("t{id}_wd"), &hi, &runs);
3534 for w in more {
3535 put(
3536 &mut e,
3537 &format!("t{id}_w{w}"),
3538 &format!("e{w}_t{id}_wr_en"),
3539 &format!("e{w}_t{id}_wr_addr"),
3540 &format!("e{w}_t{id}_wr_n"),
3541 &format!("e{w}_t{id}_wr_lw"),
3542 &format!("e{w}_t{id}_wr_data"),
3543 &format!("e{w}_t{id}_wr_hi"),
3544 &widths_or_element(tensor_widths(*w, Port::TensorWr(t.id))),
3545 );
3546 }
3547 }
3548 None => put(&mut e, &format!("t{id}_wh"), &format!("t{id}_hw"), "host_addr", "1", "4'd0", "host_wdata", &zero_hi, &BTreeSet::from([1])),
3549 }
3550 let first = match readers.first() {
3551 Some(r0) => format!("t{id}_hr ? host_addr : e{r0}_t{id}_rd_addr"),
3552 None => "host_addr".to_string(),
3553 };
3554 let mut first_runs = readers.first().map(|r0| tensor_widths(*r0, Port::TensorRd(t.id))).unwrap_or_default();
3556 first_runs.insert(1);
3557 e.l(memory_read(&format!("t{id}_rd0"), &mem, &first, width, banks, ctx.lat, &first_runs));
3558 if let Some((r0, more)) = readers.split_first() {
3559 e.l(format!(" assign e{r0}_t{id}_rd_data = t{id}_rd0;"));
3560 if banks > 1 {
3561 e.l(format!(" assign e{r0}_t{id}_rd_hi = t{id}_rd0_hi;"));
3562 }
3563 for r in more {
3564 e.l(memory_read(&format!("t{id}_rd_e{r}"), &mem, &format!("e{r}_t{id}_rd_addr"), width, banks, ctx.lat, &widths_or_element(tensor_widths(*r, Port::TensorRd(t.id)))));
3565 e.l(format!(" assign e{r}_t{id}_rd_data = t{id}_rd_e{r};"));
3566 if banks > 1 {
3567 e.l(format!(" assign e{r}_t{id}_rd_hi = t{id}_rd_e{r}_hi;"));
3568 }
3569 }
3570 }
3571 host_words.push(format!("(host_sel == 16'd{id}) ? t{id}_rd0 :"));
3572 }
3573 e.l(format!(" wire [15:0] host_sel;{}", delayed("host_sel", "host_tensor", 16, ctx.lat)));
3575 e.l(format!(" assign host_rdata = {} 64'd0;", host_words.join(" ")));
3576 let mut driven_prod: BTreeSet<BufferId> = BTreeSet::new();
3578 let mut driven_cons: BTreeSet<BufferId> = BTreeSet::new();
3579 let mut driven_wr: BTreeSet<BufferId> = BTreeSet::new();
3580 let mut driven_rd: BTreeSet<BufferId> = BTreeSet::new();
3581 for (_, plan) in &mine {
3582 let u = buffer_uses(&plan.micro);
3583 driven_prod.extend(u.produces.iter().copied());
3584 driven_cons.extend(u.consumes.iter().copied());
3585 driven_wr.extend(u.writes.iter().copied());
3586 driven_rd.extend(u.reads.iter().copied());
3587 }
3588 driven_wr.extend(bus_fed.keys().copied());
3589 for b in &bufs {
3590 let k = b.id.idx();
3591 if !driven_prod.contains(&b.id) {
3592 e.l(format!(" assign f{k}_reserve = 1'b0; assign f{k}_reserve_n = 0; assign f{k}_push = 1'b0; assign f{k}_push_n = 0;"));
3593 }
3594 if !driven_cons.contains(&b.id) {
3595 e.l(format!(" assign f{k}_pop = 1'b0; assign f{k}_pop_n = 0;"));
3596 }
3597 if !driven_wr.contains(&b.id) {
3598 e.l(format!(" assign f{k}_wr_en = 1'b0; assign f{k}_wr_slot = 0; assign f{k}_wr_elem = 0; assign f{k}_wr_data = 64'd0;"));
3599 if banks > 1 {
3600 e.l(format!(" assign f{k}_wr_n = 1; assign f{k}_wr_lw = 0; assign f{k}_wr_hi = 0;"));
3601 }
3602 }
3603 let nrd = readers
3604 .get(&ctx.template(b.id))
3605 .copied()
3606 .unwrap_or(1)
3607 .max(1);
3608 for r in 0..nrd {
3609 if !driven_rd.contains(&b.id) {
3610 e.l(format!(
3611 " assign f{k}_rd{r}_slot = 0; assign f{k}_rd{r}_elem = 0;"
3612 ));
3613 }
3614 }
3615 }
3616 for (b, g) in &bus_fed {
3618 let k = b.idx();
3619 e.l(format!(" assign f{k}_wr_en = bus_g{0}_push_b{k}; assign f{k}_wr_slot = f{k}_fill_slot0; assign f{k}_wr_elem = bus_g{0}_dout_b{k}[{1}:64]; assign f{k}_wr_data = bus_g{0}_dout_b{k}[63:0];", g.idx(), 64 + bits_for(ctx.page_elems(*b))));
3620 if banks > 1 {
3621 e.l(format!(" assign f{k}_wr_n = 1; assign f{k}_wr_lw = 0; assign f{k}_wr_hi = 0;"));
3622 }
3623 }
3624 e.l(format!(
3625 " assign done = {};",
3626 if dones.is_empty() {
3627 "1'b1".to_string()
3628 } else {
3629 dones.join(" & ")
3630 }
3631 ));
3632 e.l("endmodule");
3633 let modules: String = pools
3634 .iter()
3635 .enumerate()
3636 .map(|(i, pool)| print_pool(&format!("pool_{}_{}_{i}", site.x, site.y), pool.words, pool.width, banks, ctx.lat, pool.nread, &pool.wr, &pool.rd))
3637 .collect();
3638 Ok(format!("{modules}{}", e.s))
3639}
3640
3641trait SemIdx {
3642 fn idx_str(&self) -> String;
3643}
3644impl SemIdx for Semaphore {
3645 fn idx_str(&self) -> String {
3646 self.id.idx().to_string()
3647 }
3648}
3649
3650fn ident(s: &str) -> String {
3651 s.chars()
3652 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
3653 .collect()
3654}
3655
3656fn print_top(ctx: &Ctx, plans: &[EnginePlan], sites: &[Site]) -> Result<String, SpatialLowerError> {
3657 let p = ctx.p;
3658 let pl = p.placement.as_ref().unwrap();
3659 let mut e = Emit { s: String::new() };
3660 e.l("module spatial_top(input clk, input rst, input start, output done, output reg [31:0] cycles,");
3664 e.l(" input host_we, input host_re, input [15:0] host_site, input [15:0] host_tensor, input [31:0] host_addr,");
3665 if ctx.halts.is_empty() {
3666 e.l(" input [63:0] host_wdata, output [63:0] host_rdata);");
3667 } else {
3668 e.l(" input [63:0] host_wdata, output [63:0] host_rdata, input link_go, output halted, output [15:0] halt_at);");
3669 e.l(" reg [7:0] link_n; always @(posedge clk) if (rst) link_n <= 8'd0; else if (link_go) link_n <= link_n + 8'd1;");
3671 }
3672 for (ei, plan) in plans.iter().enumerate() {
3674 for s in sem_incs(&plan.micro) {
3675 e.l(format!(
3676 " wire e{ei}_s{0}_inc_remote; wire [15:0] e{ei}_s{0}_inc_n;",
3677 s.idx()
3678 ));
3679 }
3680 for (s, g) in mcast_valids(&plan.micro) {
3681 e.l(format!(" wire e{ei}_mv{}_g{};", s.idx(), g.idx()));
3682 }
3683 for g in mcast_sends(&plan.micro) {
3684 e.l(format!(
3685 " wire e{ei}_mc{0}_send; wire [79:0] e{ei}_mc{0}_din;",
3686 g.idx()
3687 ));
3688 }
3689 }
3690 for g in &pl.groups {
3692 let n = g.rect.len() - 1;
3693 let sender = plans
3694 .iter()
3695 .enumerate()
3696 .find(|(_, pl)| mcast_sends(&pl.micro).contains(&g.id))
3697 .map(|(ei, _)| ei);
3698 let Some(sender) = sender else { continue };
3699 e.l(format!(
3700 " wire [{}:0] bus_g{}_push; wire [79:0] bus_g{}_dout;",
3701 n - 1,
3702 g.id.idx(),
3703 g.id.idx()
3704 ));
3705 e.l(format!(" mcast_bus_prim_{n} mcast_bus_{}(.clk(clk), .rst(rst), .send(e{sender}_mc{0}_send), .din(e{sender}_mc{0}_din), .push(bus_g{0}_push), .dout(bus_g{0}_dout));", g.id.idx()));
3706 }
3707 let mut dones = Vec::new();
3708 let mut site_words = Vec::new();
3709 let mut halted_sites: Vec<Site> = Vec::new();
3710 for (si, site) in sites.iter().enumerate() {
3711 let mut conn = vec![
3712 ".clk(clk)".to_string(),
3713 ".rst(rst)".to_string(),
3714 ".start(start)".to_string(),
3715 format!(".done(site_done_{}_{})", site.x, site.y),
3716 ".host_we(host_we)".to_string(),
3717 format!(".host_re(host_re && host_site == 16'd{si})"),
3718 ".host_tensor(host_tensor)".to_string(),
3719 ".host_addr(host_addr)".to_string(),
3720 ".host_wdata(host_wdata)".to_string(),
3721 format!(".host_rdata(host_rdata_s{si})"),
3722 ];
3723 e.l(format!(" wire [63:0] host_rdata_s{si};"));
3724 site_words.push(format!("(host_from == 16'd{si}) ? host_rdata_s{si} :"));
3725 e.l(format!(" wire site_done_{}_{};", site.x, site.y));
3726 dones.push(format!("site_done_{}_{}", site.x, site.y));
3727 if plans.iter().any(|pl| pl.site == *site && halts_in(&pl.micro)) {
3728 e.l(format!(" wire site_halted_{0}_{1}; wire [15:0] site_halt_at_{0}_{1};", site.x, site.y));
3729 conn.push(format!(".link_n(link_n), .halted(site_halted_{0}_{1}), .halt_at(site_halt_at_{0}_{1})", site.x, site.y));
3730 halted_sites.push(*site);
3731 }
3732 for (ei, plan) in plans.iter().enumerate() {
3734 for m in &plan.micro {
3735 match m {
3736 Micro::Fixed(Fixed::SemInc { sem, at, .. }) | Micro::Then(Fixed::SemInc { sem, at, .. }) if *at == *site => {
3737 conn.push(format!(
3738 ".inc_s{0}_from_e{ei}(e{ei}_s{0}_inc_remote)",
3739 sem.idx()
3740 ));
3741 conn.push(format!(
3742 ".inc_s{0}_n_from_e{ei}(e{ei}_s{0}_inc_n)",
3743 sem.idx()
3744 ));
3745 }
3746 Micro::Fixed(Fixed::McastSetValid { sem, group }) | Micro::Then(Fixed::McastSetValid { sem, group }) => {
3747 let g = &pl.groups[group.idx()];
3748 if g.rect.sites().contains(site) && g.sender != *site {
3749 conn.push(format!(
3750 ".valid_s{0}_from_e{ei}_g{1}(e{ei}_mv{0}_g{1})",
3751 sem.idx(),
3752 group.idx()
3753 ));
3754 }
3755 }
3756 _ => {}
3757 }
3758 }
3759 }
3760 for (ei, plan) in plans.iter().enumerate().filter(|(_, pl)| pl.site == *site) {
3762 for s in sem_incs(&plan.micro) {
3763 conn.push(format!(
3764 ".e{ei}_s{0}_inc_remote(e{ei}_s{0}_inc_remote)",
3765 s.idx()
3766 ));
3767 conn.push(format!(".e{ei}_s{0}_inc_n(e{ei}_s{0}_inc_n)", s.idx()));
3768 }
3769 for (s, g) in mcast_valids(&plan.micro) {
3770 conn.push(format!(
3771 ".e{ei}_mv{0}_g{1}(e{ei}_mv{0}_g{1})",
3772 s.idx(),
3773 g.idx()
3774 ));
3775 }
3776 for g in mcast_sends(&plan.micro) {
3777 conn.push(format!(".e{ei}_mc{0}_send(e{ei}_mc{0}_send)", g.idx()));
3778 conn.push(format!(".e{ei}_mc{0}_din(e{ei}_mc{0}_din)", g.idx()));
3779 }
3780 }
3781 for st in &p.stages {
3783 if st.site == Some(*site) {
3784 if let StageKind::Recv { group } = st.kind {
3785 let g = &pl.groups[group.idx()];
3786 let receivers: Vec<Site> = g
3787 .rect
3788 .sites()
3789 .into_iter()
3790 .filter(|s| *s != g.sender)
3791 .collect();
3792 let idx = receivers.iter().position(|s| s == site).unwrap_or(0);
3793 for b in &st.outs {
3794 conn.push(format!(
3795 ".bus_g{0}_push_b{1}(bus_g{0}_push[{idx}])",
3796 group.idx(),
3797 b.idx()
3798 ));
3799 conn.push(format!(
3800 ".bus_g{0}_dout_b{1}(bus_g{0}_dout)",
3801 group.idx(),
3802 b.idx()
3803 ));
3804 }
3805 }
3806 }
3807 }
3808 e.l(format!(
3809 " site_{}_{} site_inst_{}_{}({});",
3810 site.x,
3811 site.y,
3812 site.x,
3813 site.y,
3814 conn.join(", ")
3815 ));
3816 }
3817 e.l(format!(" assign done = {};", dones.join(" & ")));
3818 e.l(format!(" wire [15:0] host_from;{}", delayed("host_from", "host_site", 16, ctx.lat)));
3819 e.l(format!(" assign host_rdata = {} 64'd0;", site_words.join(" ")));
3820 e.l(" reg started;");
3821 e.l(" always @(posedge clk) begin");
3822 e.l(" if (rst) begin cycles <= 0; started <= 1'b0; end");
3823 if ctx.halts.is_empty() {
3824 e.l(" else begin if (start) started <= 1'b1; if ((start || started) && !done) cycles <= cycles + 1; end");
3825 } else {
3826 e.l(" else begin if (start) started <= 1'b1; if ((start || started) && !done && !halted) cycles <= cycles + 1; end");
3828 }
3829 e.l(" end");
3830 if !ctx.halts.is_empty() {
3831 let names = |f: &str| -> Vec<String> { halted_sites.iter().map(|s| format!("{f}_{}_{}", s.x, s.y)).collect() };
3832 e.l(format!(" assign halted = {};", names("site_halted").join(" | ")));
3833 let at: String = halted_sites.iter().map(|s| format!("site_halted_{0}_{1} ? site_halt_at_{0}_{1} : ", s.x, s.y)).collect();
3834 e.l(format!(" assign halt_at = {at}16'd0;"));
3835 }
3836 e.l("endmodule");
3837 Ok(e.s)
3838}
3839
3840fn print_testbench(ctx: &Ctx, sites: &[Site], predicted: u64) -> String {
3841 let p = ctx.p;
3842 let pl = p.placement.as_ref().unwrap();
3843 let mut outs: Vec<&TensorDecl> =
3847 p.tensors.iter().filter(|t| matches!(t.role, TensorRole::Output | TensorRole::State)).collect();
3848 if outs.is_empty() {
3849 outs.extend(p.output_tensor());
3850 }
3851 let (tr, tc) = (ctx.tr, ctx.tc);
3852 let owner_of = |out: &TensorDecl| -> BTreeMap<usize, Site> {
3853 let section = p.sections.iter().find(|s| s.output == Some(out.id));
3854 let mut owner = BTreeMap::new();
3855 for (site, items) in &pl.items_of_site {
3856 for it in items {
3857 if section.is_some_and(|s| !(s.items.0..s.items.1).contains(&it.idx())) {
3858 continue;
3859 }
3860 let r = p.work.items[it.idx()].out;
3861 for ty in r.row0_t..r.row0_t + r.h_t {
3862 for tx in r.col0_t..r.col0_t + r.w_t {
3863 owner.insert(ty * out.tiles.cols_t + tx, *site);
3864 }
3865 }
3866 }
3867 }
3868 owner
3869 };
3870 let mut e = Emit { s: String::new() };
3871 e.l("`timescale 1ns/1ps");
3872 e.l("module tb;");
3873 e.l(" reg clk = 0, rst = 1, start = 0; wire done; wire [31:0] cycles;");
3874 e.l(" spatial_top dut(.clk(clk), .rst(rst), .start(start), .done(done), .cycles(cycles), .host_we(1'b0), .host_re(1'b0),");
3875 e.l(" .host_site(16'd0), .host_tensor(16'd0), .host_addr(32'd0), .host_wdata(64'd0), .host_rdata());");
3876 e.l(" always #5 clk = ~clk;");
3877 e.l(" integer guard;");
3878 e.l(" initial begin");
3879 e.l(" #12 rst = 0; #10 start = 1; #10 start = 0;");
3880 e.l(" guard = 0;");
3881 let guard_bound = predicted.saturating_mul(8).max(20_000);
3887 e.l(format!(
3888 " while (!done && guard < {guard_bound}) begin #10 guard = guard + 1; end"
3889 ));
3890 e.l(" #10;");
3891 e.l(" $display(\"CYCLES %0d\", cycles);");
3892 for out in outs {
3893 let owner = owner_of(out);
3894 for i in 0..out.elems.rows {
3895 for j in 0..out.elems.cols {
3896 let tile = (i / tr) * out.tiles.cols_t + j / tc;
3897 let site = owner.get(&tile).copied().unwrap_or(sites[0]);
3898 let addr = host_word(p, out, i, j);
3899 let word = memory_word(
3900 &format!("dut.site_inst_{}_{}.mem_t{}", site.x, site.y, out.id.idx()),
3901 addr,
3902 tensor_width(out, &ctx.ranges),
3903 ctx.banks,
3904 );
3905 e.l(format!(" $display(\"OUT {} {} %0d\", $signed({word}));", out.id.idx(), i * out.elems.cols + j));
3906 }
3907 }
3908 }
3909 e.l(format!(" if (done) $display(\"PASS: spatial_top done\"); else $display(\"FAIL: timeout after %0d cycles (bound {guard_bound}, model {predicted})\", cycles);"));
3910 e.l(" $finish;");
3911 e.l(" end");
3912 e.l("endmodule");
3913 e.s
3914}
3915
3916#[cfg(test)]
3917#[path = "../../../../logicaffeine_tests/unit/logicaffeine_compile/spatial/lower/fabric/tests.rs"]
3918mod tests;