Skip to main content

logicaffeine_compile/codegen_sva/
shuttle_wrapper.rs

1//! Packing EVERY signed-off macro into ONE Caravel shuttle submission.
2//!
3//! A shuttle slot buys one 2920 x 3520 um user area, and until now exactly one block has ever
4//! occupied it. The other fifteen signoff-clean macros were "manufacturable" only in the sense
5//! that each *could* be packaged — none was, and sixteen slots is not a plan.
6//!
7//! They do not need sixteen slots. Measured from their hardened LEFs, the sixteen clean macros
8//! total **4.771 mm^2** against the **10.278 mm^2** user area: 46% occupancy, all of them, in one
9//! submission. Every one already has a hardened `.lef`/`.gds`/`.lib` from the run its signoff
10//! ledger entry vouches for, so nothing has to be re-hardened to do this — only placed, powered
11//! and wired.
12//!
13//! WIRING IS THE REAL WORK. Caravel speaks Wishbone; these macros do not. Their ports are small
14//! and regular though (3 to 18, mostly `clk`/`rst` plus a couple of data words), so the bridge is
15//! mechanical: give each macro an address window, map its inputs to write registers and its
16//! outputs to read registers, and share the clock and reset. That is generated here rather than
17//! hand-written sixteen times, because hand-writing it sixteen times is how a submission ends up
18//! instantiating a module nothing defines.
19
20use crate::codegen_sva::pdk_target::UserArea;
21
22/// One port of a macro, as its RTL declares it.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct MacroPort {
25    pub name: &'static str,
26    pub width: u32,
27    pub is_input: bool,
28}
29
30/// A hardened, signoff-clean macro and the footprint its LEF reports.
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct ShuttleMacro {
33    pub name: &'static str,
34    /// Footprint in micrometres, from the macro's own LEF `SIZE` line — never estimated.
35    pub width_um: f64,
36    pub height_um: f64,
37    /// Data ports, EXCLUDING `clk` and `rst`, which are driven from the Caravel harness.
38    pub ports: &'static [MacroPort],
39    /// Whether the macro takes a reset (some are free-running).
40    pub has_rst: bool,
41}
42
43impl ShuttleMacro {
44    pub fn area_mm2(&self) -> f64 {
45        self.width_um * self.height_um / 1.0e6
46    }
47    /// 32-bit Wishbone words needed to carry every input, and every output.
48    pub fn in_words(&self) -> u32 {
49        self.ports
50            .iter()
51            .filter(|p| p.is_input)
52            .map(|p| p.width.div_ceil(32))
53            .sum()
54    }
55    pub fn out_words(&self) -> u32 {
56        self.ports
57            .iter()
58            .filter(|p| !p.is_input)
59            .map(|p| p.width.div_ceil(32))
60            .sum()
61    }
62}
63
64/// Where a macro sits in the user area, in LEF orientation terms.
65#[derive(Debug, Clone, PartialEq)]
66pub struct Placement {
67    pub name: &'static str,
68    pub x_um: f64,
69    pub y_um: f64,
70    /// Wishbone base address for this macro's register window.
71    pub base: u32,
72}
73
74/// Why a set of macros cannot share one user area.
75#[derive(Debug, Clone, PartialEq)]
76pub enum ShuttleUnplaceable {
77    /// Total footprint exceeds the vehicle even before spacing.
78    AreaExceeded { needed_mm2: f64, available_mm2: f64 },
79    /// One macro is individually wider or taller than the user area.
80    MacroTooLarge { name: &'static str, w: f64, h: f64 },
81    /// Shelf packing ran out of vertical room at the requested keep-out.
82    DoesNotPack {
83        placed: usize,
84        total: usize,
85        keepout_um: f64,
86    },
87    /// The register windows would overflow the address space allotted per macro.
88    AddressOverflow {
89        name: &'static str,
90        words: u32,
91        per_macro_words: u32,
92    },
93}
94
95/// Micrometres of keep-out around every macro.
96///
97/// Macros need room for their power hookup and for signal routing to escape; abutting them makes
98/// the wrapper unroutable no matter how much total area is free. 50 um is the spacing the single
99/// -macro wrapper already uses successfully (`macro.cfg` places `mprj` at 60,880 inside a
100/// 2920x3520 area) and is kept here rather than tightened, because a packing failure costs a
101/// multi-hour route to discover.
102pub const MACRO_KEEPOUT_UM: f64 = 50.0;
103
104/// Address window per macro. 0x100 gives 64 registers — far more than the widest macro here
105/// needs (18 ports), and keeps each base a readable multiple in a memory map a human debugs.
106pub const MACRO_ADDR_STRIDE: u32 = 0x100;
107const WB_BASE: u32 = 0x3000_0000;
108
109/// Shelf-pack the macros into the user area, tallest first.
110///
111/// Tallest-first shelf packing is the right shape here and not merely convenient: the macros vary
112/// by two orders of magnitude in area (0.019 to 1.140 mm^2), and packing the tall ones first keeps
113/// each shelf's wasted height bounded by the shortest macro on it. At 46% occupancy this has
114/// enormous slack; the planner still REFUSES rather than overlapping if it runs out.
115pub fn plan_shuttle(
116    macros: &[ShuttleMacro],
117    area: UserArea,
118) -> Result<Vec<Placement>, ShuttleUnplaceable> {
119    let avail = area.mm2();
120    let needed: f64 = macros.iter().map(|m| m.area_mm2()).sum();
121    if needed > avail {
122        return Err(ShuttleUnplaceable::AreaExceeded {
123            needed_mm2: needed,
124            available_mm2: avail,
125        });
126    }
127    let (aw, ah) = (area.width_um, area.height_um);
128    for m in macros {
129        if m.width_um + 2.0 * MACRO_KEEPOUT_UM > aw || m.height_um + 2.0 * MACRO_KEEPOUT_UM > ah {
130            return Err(ShuttleUnplaceable::MacroTooLarge {
131                name: m.name,
132                w: m.width_um,
133                h: m.height_um,
134            });
135        }
136    }
137    for (i, m) in macros.iter().enumerate() {
138        let words = m.in_words().max(m.out_words());
139        let per = MACRO_ADDR_STRIDE / 4;
140        if words > per {
141            return Err(ShuttleUnplaceable::AddressOverflow {
142                name: m.name,
143                words,
144                per_macro_words: per,
145            });
146        }
147        let _ = i;
148    }
149
150    let mut order: Vec<&ShuttleMacro> = macros.iter().collect();
151    order.sort_by(|a, b| b.height_um.total_cmp(&a.height_um).then(a.name.cmp(b.name)));
152
153    let mut out = Vec::new();
154    let (mut x, mut y, mut shelf_h) = (MACRO_KEEPOUT_UM, MACRO_KEEPOUT_UM, 0.0f64);
155    for m in &order {
156        let (w, h) = (m.width_um, m.height_um);
157        if x + w + MACRO_KEEPOUT_UM > aw {
158            x = MACRO_KEEPOUT_UM;
159            y += shelf_h + MACRO_KEEPOUT_UM;
160            shelf_h = 0.0;
161        }
162        if y + h + MACRO_KEEPOUT_UM > ah {
163            return Err(ShuttleUnplaceable::DoesNotPack {
164                placed: out.len(),
165                total: macros.len(),
166                keepout_um: MACRO_KEEPOUT_UM,
167            });
168        }
169        // Address by the macro's position in the CALLER's order, so the memory map is stable
170        // against any future change to the packing heuristic.
171        let idx = macros
172            .iter()
173            .position(|c| c.name == m.name)
174            .expect("macro in the input set");
175        out.push(Placement {
176            name: m.name,
177            x_um: x,
178            y_um: y,
179            base: WB_BASE + (idx as u32) * MACRO_ADDR_STRIDE,
180        });
181        x += w + MACRO_KEEPOUT_UM;
182        shelf_h = shelf_h.max(h);
183    }
184    Ok(out)
185}
186
187/// OpenLane `macro.cfg` lines: `<instance> <x> <y> <orientation>`.
188pub fn emit_macro_cfg(placed: &[Placement]) -> String {
189    let mut s = String::new();
190    for p in placed {
191        s.push_str(&format!("{}_i {:.0} {:.0} N\n", p.name, p.x_um, p.y_um));
192    }
193    s
194}
195
196/// `FP_PDN_MACRO_HOOKS` — every macro must be hooked to the user-area supply, or the power
197/// connections check fails the submission outright.
198pub fn emit_pdn_hooks(placed: &[Placement]) -> String {
199    placed
200        .iter()
201        .map(|p| format!("{}_i vccd1 vssd1 vccd1 vssd1", p.name))
202        .collect::<Vec<_>>()
203        .join(", ")
204}
205
206const fn i(name: &'static str, width: u32) -> MacroPort {
207    MacroPort {
208        name,
209        width,
210        is_input: true,
211    }
212}
213const fn o(name: &'static str, width: u32) -> MacroPort {
214    MacroPort {
215        name,
216        width,
217        is_input: false,
218    }
219}
220
221/// Every signoff-clean macro, with the footprint its own hardened LEF reports.
222///
223/// Dimensions are read from `results/final/lef/<macro>.lef` of the run whose `gds_bytes` the
224/// signoff ledger vouches for — the same race-free resolution the submission assembler uses, so
225/// a placement can never be planned against a build that is not the one signed off.
226/// `shuttle_wrapper_lock` re-derives every number here from those LEFs.
227pub const CLEAN_MACROS: &[ShuttleMacro] = &[
228    ShuttleMacro {
229        name: "systolic_pipe8_realbist",
230        width_um: 1062.6,
231        height_um: 1073.3,
232        ports: &[o("sig", 1)],
233        has_rst: true,
234    },
235    ShuttleMacro {
236        name: "fabric_horner_seq",
237        width_um: 1028.8,
238        height_um: 1039.5,
239        ports: &[i("in0", 64), o("result", 64), o("done", 1)],
240        has_rst: true,
241    },
242    ShuttleMacro {
243        name: "fabric_soc",
244        width_um: 783.2,
245        height_um: 793.9,
246        ports: &[i("in0", 64), o("result", 64), o("done", 1)],
247        has_rst: true,
248    },
249    ShuttleMacro {
250        name: "soft_core_scalar",
251        width_um: 649.0,
252        height_um: 659.7,
253        ports: &[i("start", 1), o("result", 64), o("done", 1)],
254        has_rst: true,
255    },
256    ShuttleMacro {
257        name: "systolic_tile_realbist",
258        width_um: 602.0,
259        height_um: 612.7,
260        ports: &[o("sig", 1)],
261        has_rst: true,
262    },
263    ShuttleMacro {
264        name: "gpu_simt",
265        width_um: 591.0,
266        height_um: 601.7,
267        ports: &[i("in0", 64), o("result", 64), o("done", 1)],
268        has_rst: true,
269    },
270    ShuttleMacro {
271        name: "gemm_mac_realbist",
272        width_um: 529.5,
273        height_um: 540.2,
274        ports: &[o("sig", 1)],
275        has_rst: true,
276    },
277    ShuttleMacro {
278        name: "infinichip",
279        width_um: 350.0,
280        height_um: 350.0,
281        ports: &[i("in0", 64), o("result", 64), o("done", 1)],
282        has_rst: true,
283    },
284    ShuttleMacro {
285        name: "dpu_ingest",
286        width_um: 320.0,
287        height_um: 320.0,
288        ports: &[i("in0", 64), o("result", 64), o("done", 1)],
289        has_rst: true,
290    },
291    ShuttleMacro {
292        name: "dram_executor_wb",
293        width_um: 300.0,
294        height_um: 300.0,
295        ports: &[i("cmd", 64), o("la", 64)],
296        has_rst: true,
297    },
298    ShuttleMacro {
299        name: "nested_tri",
300        width_um: 204.8,
301        height_um: 215.5,
302        ports: &[i("in0", 64), o("result", 64), o("done", 1)],
303        has_rst: true,
304    },
305    ShuttleMacro {
306        name: "cgra_sumto",
307        width_um: 187.2,
308        height_um: 198.0,
309        ports: &[i("in0", 64), o("result", 64), o("done", 1)],
310        has_rst: true,
311    },
312    ShuttleMacro {
313        name: "decision_hft_realbist",
314        width_um: 178.6,
315        height_um: 189.3,
316        ports: &[o("sig", 1)],
317        has_rst: true,
318    },
319    ShuttleMacro {
320        name: "decision_hft_bist",
321        width_um: 159.1,
322        height_um: 169.8,
323        ports: &[o("sig", 1)],
324        has_rst: true,
325    },
326    ShuttleMacro {
327        name: "rev_alu_core",
328        width_um: 150.9,
329        height_um: 161.6,
330        ports: &[i("op", 3), o("a", 32), o("b", 32)],
331        has_rst: false,
332    },
333    ShuttleMacro {
334        name: "dram_executor",
335        width_um: 132.4,
336        height_um: 143.1,
337        ports: &[
338            i("cmd_word", 64),
339            i("cmd_valid", 1),
340            o("issue", 1),
341            o("op", 3),
342            o("bank", 5),
343            o("row", 32),
344        ],
345        has_rst: true,
346    },
347];