1use std::ops::Deref;
16
17use super::backend::{serialize_grid, BackendArtifact, TargetBackend};
18use super::fabric_grid::Grid;
19use super::fabric_target::FabricTarget;
20use super::fabric_target::Precision;
21use crate::spatial::rdu_chip::{Fact, Provenance, RduChip, HBM_STACKS};
22use crate::spatial::roofs::{
23 ComputeRoof, Engine, MemLevel, MemRoof, Rate, Roofs, Scope, StreamMix, Transfer,
24};
25
26const ISCA17: &str = "Prabhakar et al., Plasticine, ISCA 2017 (DOI 10.1145/3079856.3080256)";
27const SARA: &str = "Zhang, Scaling a Reconfigurable Dataflow Accelerator (SARA), Stanford 2020";
28const TOOLCHAIN_LEDGER: &str = "benchmarks/silicon/plasticine_toolchain.json";
29
30const RETICLE_LIMIT_MM2: f64 = 858.0;
32
33#[derive(Clone, Debug, PartialEq, serde::Serialize)]
35pub struct DramSpec {
36 pub channels: usize,
37 pub kind: &'static str,
38 pub gbps: f64,
40}
41
42#[derive(Clone, Debug, PartialEq, serde::Serialize)]
44pub struct PlasticineTarget {
45 pub base: FabricTarget,
47 pub pcu_rows: usize,
49 pub pcu_cols: usize,
50 pub pcu_lanes: usize,
52 pub pcu_stages: usize,
54 pub pcu_regs_per_stage: usize,
56 pub pcu_scalar_io: (usize, usize),
58 pub pcu_vector_io: (usize, usize),
59 pub pmu_vector_io: (usize, usize),
61 pub pcu_control_io: Option<(usize, usize)>,
62 pub pcu_counters: Option<usize>,
64 pub pmu_banks: usize,
66 pub pmu_kib: usize,
67 pub pmu_stages: usize,
68 pub dram: DramSpec,
69 pub clock_ghz: f64,
70 pub area_mm2: f64,
71 pub power_w: Option<f64>,
72 pub peak_fp32_tflops: f64,
74 pub cite: &'static str,
76 #[serde(skip)]
79 rates: RateFacts,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq)]
85struct RateFacts {
86 dram: (Provenance, StreamMix, &'static str),
87 clock: (Provenance, &'static str),
88 peak_fp32: (Provenance, &'static str),
89 sources: &'static [&'static str],
90}
91
92impl Deref for PlasticineTarget {
93 type Target = FabricTarget;
94 fn deref(&self) -> &FabricTarget {
95 &self.base
96 }
97}
98
99impl PlasticineTarget {
100 pub fn isca17() -> PlasticineTarget {
105 PlasticineTarget {
106 base: FabricTarget::plasticine_shaped(),
107 pcu_rows: 16,
108 pcu_cols: 8,
109 pcu_lanes: 16,
110 pcu_stages: 6,
111 pcu_regs_per_stage: 6,
112 pcu_scalar_io: (6, 5),
113 pcu_vector_io: (3, 3),
114 pmu_vector_io: (3, 1),
115 pcu_control_io: None,
116 pcu_counters: None,
117 pmu_banks: 16,
118 pmu_kib: 256,
119 pmu_stages: 4,
120 dram: DramSpec { channels: 4, kind: "DDR3-1600", gbps: 51.2 },
121 clock_ghz: 1.0,
122 area_mm2: 112.8,
123 power_w: Some(49.0),
124 peak_fp32_tflops: 12.3,
125 cite: "Prabhakar et al., Plasticine, ISCA 2017 (DOI 10.1145/3079856.3080256), Tables 3/5, §3, §5",
126 rates: RateFacts {
127 dram: (Provenance::Published, StreamMix::Any, "Prabhakar et al., Plasticine, ISCA 2017 (DOI 10.1145/3079856.3080256), Tables 3/5, §3, §5: four DDR3-1600 channels, 51.2 GB/s"),
128 clock: (Provenance::Published, "Prabhakar et al., Plasticine, ISCA 2017 (DOI 10.1145/3079856.3080256), Tables 3/5, §3, §5: 1 GHz"),
129 peak_fp32: (Provenance::Published, "Prabhakar et al., Plasticine, ISCA 2017 (DOI 10.1145/3079856.3080256), Tables 3/5, §3, §5: 12.3 fp32 TFLOPS"),
130 sources: &[ISCA17],
131 },
132 }
133 }
134
135 pub fn sara_20x20() -> PlasticineTarget {
140 PlasticineTarget {
141 base: FabricTarget { name: "plasticine-sara-20x20", ..FabricTarget::plasticine_shaped() },
142 pcu_rows: 20,
143 pcu_cols: 20,
144 pcu_lanes: 16,
145 pcu_stages: 6,
146 pcu_regs_per_stage: 8,
147 pcu_scalar_io: (6, 6),
148 pcu_vector_io: (6, 6),
149 pmu_vector_io: (6, 6),
150 pcu_control_io: Some((16, 8)),
151 pcu_counters: None,
152 pmu_banks: 16,
153 pmu_kib: 256,
154 pmu_stages: 10,
155 dram: DramSpec { channels: 8, kind: "HBM2", gbps: 1000.0 },
156 clock_ghz: 1.0,
157 area_mm2: 352.0,
158 power_w: None,
159 peak_fp32_tflops: 12.3 * 200.0 / 64.0,
160 cite: "Zhang, Scaling a Reconfigurable Dataflow Accelerator (SARA), Stanford 2020, Table 3.3, §4.2; Capstan, MICRO 2021, Table 7",
161 rates: SARA_RATES,
162 }
163 }
164
165 pub fn tungsten_20x20_ddr4() -> PlasticineTarget {
171 PlasticineTarget {
172 rates: RateFacts {
173 dram: (
174 Provenance::Measured,
175 StreamMix::Read,
176 "benchmarks/silicon/plasticine_toolchain.json: Tungsten (ramulator, DDR4-2133, four channels) reports \
177 dram_read_gbps 86.5733 on Spatial's DotProduct_0, a read stream; 86.6 at the descriptor's precision",
178 ),
179 ..SARA_RATES
180 },
181 base: FabricTarget { name: "plasticine-tungsten-20x20-ddr4", ..FabricTarget::plasticine_shaped() },
182 dram: DramSpec { channels: 4, kind: "DDR4-2133 x16, ramulator (Tungsten default config); effective 86.6 GB/s as Tungsten reports on DotProduct_0", gbps: 86.6 },
183 cite: "PIR default mesh (--row=20 --col=20) + Tungsten DDR4-config.cfg (standard DDR4, channels 4, ranks 4, DDR4_2133, DDR4_8Gb_x16); bandwidth = Tungsten's own report on DotProduct_0",
184 ..PlasticineTarget::sara_20x20()
185 }
186 }
187
188 pub fn rdu(chip: RduChip) -> PlasticineTarget {
199 let facts = chip.facts();
200 let um = &facts.unit_model;
201 let mesh = facts.mesh();
202 assert_eq!(
203 facts.pcu_count.value, facts.pmu_count.value,
204 "the 1:1 checkerboard carries one PMU per PCU; a PMU count that differs is the descriptor refinement an SDK number brings"
205 );
206 let (kind, cite) = match chip {
207 RduChip::Sn40l => (
208 "HBM (generation not public)",
209 "sambanova.ai / Hot Chips 2024 (hc2024.hotchips.org) / arXiv:2405.07518 for the envelope's published numbers; the mesh descriptor is modeled: the SN40L's 1040 PCUs and 1040 PMUs on the unit model's 40-column aspect, the PCU/PMU shapes inherited from Plasticine (Prabhakar et al., ISCA 2017), 520 MiB spread over 1040 PMUs, the co-packaged HBM at an HBM2E-class modeled rate (its generation and bandwidth are not public), a modeled 1 GHz clock, two reticle-limit dies as the area; fp32 peak not published — the 640 TFLOPS bf16 peak stands as the roofline",
210 ),
211 RduChip::Sn50 => (
212 "HBM2E",
213 "sambanova.ai/products/rdu-ai-chips / Hot Chips 2026 / ServeTheHome for the envelope's published numbers; the mesh descriptor is modeled: the PCU/PMU counts inherited from the SN40L (not public for the SN50) on the unit model's 40-column aspect, the PCU/PMU shapes inherited from Plasticine (Prabhakar et al., ISCA 2017), 432 MiB spread over 1040 PMUs, the published 64 GB HBM2E at a modeled JEDEC-rate bandwidth (not public), a modeled 1 GHz clock, two max-reticle dies as the area; fp32 peak not published — the 1600 TFLOPS bf16 peak stands as the roofline",
214 ),
215 };
216 PlasticineTarget {
217 base: facts.fabric_target(),
218 pcu_rows: 2 * mesh.y,
219 pcu_cols: mesh.x,
220 pcu_lanes: um.pcu_lanes.value,
221 pcu_stages: um.pcu_stages.value,
222 pcu_regs_per_stage: 6,
223 pcu_scalar_io: (6, 5),
224 pcu_vector_io: (3, 3),
225 pmu_vector_io: (3, 1),
226 pcu_control_io: None,
227 pcu_counters: Some(um.counter_nest.value),
228 pmu_banks: um.pmu.value.banks,
229 pmu_kib: facts.sram_mib.value * 1024 / facts.pmu_count.value.max(1),
230 pmu_stages: um.pmu.value.stages,
231 dram: DramSpec {
232 channels: HBM_STACKS,
233 kind,
234 gbps: facts.hbm_gbps().value,
235 },
236 clock_ghz: facts.clock_ghz().value,
237 area_mm2: facts.dies.value as f64 * RETICLE_LIMIT_MM2,
238 power_w: None,
239 peak_fp32_tflops: facts.peak_bf16_tflops.value as f64,
240 cite,
241 rates: RateFacts {
242 dram: (
243 facts.hbm_gbps().provenance,
244 StreamMix::Any,
245 facts.hbm_gbps().cite,
246 ),
247 clock: (facts.clock_ghz().provenance, facts.clock_ghz().cite),
248 peak_fp32: (Provenance::Modeled, rdu_roofline_cite(chip)),
249 sources: facts.cites,
250 },
251 }
252 }
253
254 pub fn sn40l_rdu() -> PlasticineTarget {
256 PlasticineTarget::rdu(RduChip::Sn40l)
257 }
258
259 pub fn sn50_rdu() -> PlasticineTarget {
261 PlasticineTarget::rdu(RduChip::Sn50)
262 }
263
264 pub fn rdu_provenance(chip: RduChip) -> Vec<(&'static str, Fact<f64>)> {
269 let t = PlasticineTarget::rdu(chip);
270 let facts = chip.facts();
271 let counts: &'static str = match chip {
272 RduChip::Sn40l => "not public as a mesh aspect; the published 1040 PCUs and 1040 PMUs (Hot Chips 2024 / arXiv:2405.07518) laid out on the unit model's modeled 40-column aspect, PMUs interleaved 1:1",
273 RduChip::Sn50 => "not public; the SN50 describes a sea of PCUs and PMUs without a count — the SN40L's 1040 + 1040 inherited, laid out on the unit model's modeled 40-column aspect, PMUs interleaved 1:1",
274 };
275 let pmu_kib: &'static str = match chip {
276 RduChip::Sn40l => "modeled: the published 520 MiB on-chip SRAM (arXiv:2405.07518) spread over the published 1040 PMUs — the per-PMU capacity is not tabulated",
277 RduChip::Sn50 => "modeled: the published 432 MB on-chip SRAM (sambanova.ai/products/rdu-ai-chips) spread over the modeled 1040 PMUs — the per-PMU capacity is not public",
278 };
279 let hbm = facts.hbm_gbps().cite;
280 let roofline = rdu_roofline_cite(chip);
281 let area: &'static str = match chip {
282 RduChip::Sn40l => "not public; modeled from the published two dies (Hot Chips 2024) at the 26 mm × 33 mm reticle limit (858 mm² each)",
283 RduChip::Sn50 => "not public; modeled from the published two max-reticle logic dies (Hot Chips 2026) at the 26 mm × 33 mm reticle limit (858 mm² each)",
284 };
285 let um = &facts.unit_model;
286 let m = |v: f64, cite: &'static str| Fact::modeled(v, cite);
287 vec![
288 ("pcu_rows", m(t.pcu_rows as f64, counts)),
289 ("pcu_cols", m(t.pcu_cols as f64, counts)),
290 ("pcu_lanes", m(t.pcu_lanes as f64, um.pcu_lanes.cite)),
291 ("pcu_stages", m(t.pcu_stages as f64, um.pcu_stages.cite)),
292 ("pcu_regs_per_stage", m(t.pcu_regs_per_stage as f64, "not public; inherited from Plasticine (Prabhakar et al., ISCA 2017) Table 3 — six pipeline registers per stage")),
293 ("pcu_scalar_io.0", m(t.pcu_scalar_io.0 as f64, "not public; inherited from Plasticine (Prabhakar et al., ISCA 2017) Table 3 — 6 scalar inputs per PCU")),
294 ("pcu_scalar_io.1", m(t.pcu_scalar_io.1 as f64, "not public; inherited from Plasticine (Prabhakar et al., ISCA 2017) Table 3 — 5 scalar outputs per PCU")),
295 ("pcu_vector_io.0", m(t.pcu_vector_io.0 as f64, "not public; inherited from Plasticine (Prabhakar et al., ISCA 2017) Table 3 — 3 vector inputs per PCU")),
296 ("pcu_vector_io.1", m(t.pcu_vector_io.1 as f64, "not public; inherited from Plasticine (Prabhakar et al., ISCA 2017) Table 3 — 3 vector outputs per PCU")),
297 ("pmu_vector_io.0", m(t.pmu_vector_io.0 as f64, "not public; inherited from Plasticine (Prabhakar et al., ISCA 2017) Table 3 — 3 vector inputs per PMU")),
298 ("pmu_vector_io.1", m(t.pmu_vector_io.1 as f64, "not public; inherited from Plasticine (Prabhakar et al., ISCA 2017) Table 3 — 1 vector output per PMU")),
299 ("pcu_counters", m(t.pcu_counters.unwrap_or(0) as f64, um.counter_nest.cite)),
300 ("pmu_banks", m(t.pmu_banks as f64, um.pmu.cite)),
301 ("pmu_kib", m(t.pmu_kib as f64, pmu_kib)),
302 ("pmu_stages", m(t.pmu_stages as f64, um.pmu.cite)),
303 ("dram.channels", m(t.dram.channels as f64, hbm)),
304 ("dram.gbps", m(t.dram.gbps, hbm)),
305 ("clock_ghz", m(t.clock_ghz, facts.clock_ghz().cite)),
306 ("area_mm2", m(t.area_mm2, area)),
307 ("peak_fp32_tflops", m(t.peak_fp32_tflops, roofline)),
308 ]
309 }
310
311 pub fn roofs(&self) -> Roofs {
313 let r = &self.rates;
314 Roofs {
315 clock_ghz: Fact {
316 value: self.clock_ghz,
317 provenance: r.clock.0,
318 cite: r.clock.1,
319 },
320 mem: vec![MemRoof {
321 level: MemLevel::Dram,
322 mix: r.dram.1,
323 scope: Scope::Chip,
324 unit: Transfer::Bytes,
325 rate: Fact {
326 value: Rate::PerSecond(self.dram.gbps * 1e9),
327 provenance: r.dram.0,
328 cite: r.dram.2,
329 },
330 }],
331 compute: vec![ComputeRoof {
332 engine: Engine::Vector,
333 precision: Precision::Fp32,
334 scope: Scope::Chip,
335 ops: Fact {
336 value: Rate::PerSecond(self.peak_fp32_tflops * 1e12),
337 provenance: r.peak_fp32.0,
338 cite: r.peak_fp32.1,
339 },
340 }],
341 sources: r.sources,
342 }
343 }
344
345 pub fn pcus(&self) -> usize {
347 self.pcu_rows * self.pcu_cols / 2
348 }
349
350 pub fn pmus(&self) -> usize {
352 self.pcu_rows * self.pcu_cols - self.pcus()
353 }
354
355 pub fn scratchpad_kib(&self) -> usize {
357 self.pmus() * self.pmu_kib
358 }
359}
360
361const SARA_RATES: RateFacts = RateFacts {
364 dram: (Provenance::Published, StreamMix::Any, "Zhang, Scaling a Reconfigurable Dataflow Accelerator (SARA), Stanford 2020, Table 3.3, §4.2: HBM2 at 1 TB/s"),
365 clock: (Provenance::Modeled, "inherited from Plasticine (Prabhakar et al., ISCA 2017 §5): 1 GHz; the SARA configuration tabulates no clock"),
366 peak_fp32: (
367 Provenance::Derived,
368 "Prabhakar et al., Plasticine, ISCA 2017 (DOI 10.1145/3079856.3080256), Tables 3/5, §3, §5: 12.3 fp32 TFLOPS on 64 PCUs, scaled to \
369 the 200 PCUs of the 20 x 20 checkerboard: 12.3 x 200 / 64",
370 ),
371 sources: &[ISCA17, SARA, TOOLCHAIN_LEDGER],
372};
373
374fn rdu_roofline_cite(chip: RduChip) -> &'static str {
376 match chip {
377 RduChip::Sn40l => "fp32 peak not published; the published 640 TFLOPS bf16 peak (Hot Chips 2024) used as the roofline — modeled by that substitution",
378 RduChip::Sn50 => "fp32 peak not published; the published 1600 TFLOPS bf16 peak (sambanova.ai/products/rdu-ai-chips) used as the roofline — modeled by that substitution",
379 }
380}
381
382pub struct PlasticineBackend {
387 pub target: PlasticineTarget,
388}
389
390impl PlasticineBackend {
391 pub fn isca17() -> Self {
392 PlasticineBackend {
393 target: PlasticineTarget::isca17(),
394 }
395 }
396 pub fn sara_20x20() -> Self {
397 PlasticineBackend {
398 target: PlasticineTarget::sara_20x20(),
399 }
400 }
401 const PENDING_REASON: &'static str =
402 "Spatial source is emitted per counted loop by codegen_sva::spatial_emit (P4, `largo emit plasticine`); a placed straight-line Grid has no Spatial/PIR form yet — this is its certified neutral placement, not a Plasticine program";
403}
404
405impl TargetBackend for PlasticineBackend {
406 fn name(&self) -> &str {
407 self.target.name
408 }
409 fn target(&self) -> &FabricTarget {
410 &self.target.base
411 }
412 fn emit_grid(&self, _name: &str, g: &Grid) -> BackendArtifact {
413 BackendArtifact::SdkGated {
414 neutral_placement: serialize_grid(g),
415 reason: Self::PENDING_REASON,
416 }
417 }
418 fn emit_config_grid(&self, _name: &str, grids: &[&Grid], _cfg_width: u32) -> BackendArtifact {
419 let placement = grids
420 .iter()
421 .map(|g| serialize_grid(g))
422 .collect::<Vec<_>>()
423 .join("---\n");
424 BackendArtifact::SdkGated {
425 neutral_placement: placement,
426 reason: Self::PENDING_REASON,
427 }
428 }
429}