Skip to main content

logicaffeine_compile/spatial/
part.rs

1//! What a programmable part holds, as facts with sources: its logic, its flip-flops, its multipliers and every
2//! kind of on-chip memory it carries, counted the way its vendor's data sheet counts them.
3//!
4//! A part is the budget a lowering spends and the capacity a pricer reads. Stated once, from the data sheet,
5//! the same numbers let a compiler refuse a fabric whose memories or multipliers the part cannot hold before a
6//! line of RTL is printed, and let the decode pricer read on-chip capacity and port bandwidth instead of
7//! restating them. Each figure is a [`Fact`]: published when the table prints it, derived when it is the
8//! table's arithmetic (four LUTs to a 7-series slice, 64 bits to a LUT), with the figure quoted in its cite.
9
10use serde::Serialize;
11
12use super::rdu_chip::{Fact, Provenance};
13
14pub const DS180: &str = "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview";
15pub const UG473: &str = "UG473 (v1.14) 7 Series FPGAs Memory Resources";
16pub const UG474: &str = "UG474 (v1.8) 7 Series FPGAs Configurable Logic Block";
17pub const UG479: &str = "UG479 (v1.10) 7 Series DSP48E1 Slice";
18pub const DS891: &str = "DS891 (v1.11.1) Zynq UltraScale+ MPSoC Data Sheet: Overview";
19pub const UG573: &str = "UG573 (v1.9) UltraScale Architecture Memory Resources";
20pub const UG574: &str = "UG574 (v1.5) UltraScale Architecture Configurable Logic Block";
21pub const UG579: &str = "UG579 (v1.7) UltraScale Architecture DSP Slice";
22pub const S10_OVERVIEW: &str = "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview";
23pub const S10_PRODUCT_TABLE: &str = "GEN-1023-1.0 (May 2015) Stratix 10 Product Table";
24
25/// The unit a part's logic is counted in.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
27pub enum LogicUnit {
28    /// A Xilinx six-input look-up table.
29    Lut6,
30    /// An Intel adaptive logic module.
31    Alm,
32}
33
34/// A kind of on-chip memory.
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
36pub enum MemoryKind {
37    /// Look-up tables a memory slice can run as RAM, counted in LUTs.
38    LutRam,
39    /// Intel's memory logic array block.
40    Mlab,
41    /// A Xilinx 36 Kb block RAM (RAMB36E1/E2), which also runs as two independent 18 Kb halves.
42    Block36,
43    /// Intel's 20 Kb block.
44    M20k,
45    /// A Xilinx UltraRAM (URAM288).
46    Ultra,
47}
48
49impl MemoryKind {
50    /// Whether the memory is a dedicated block, rather than logic the fabric would otherwise spend as gates.
51    pub fn is_block(self) -> bool {
52        matches!(self, MemoryKind::Block36 | MemoryKind::M20k | MemoryKind::Ultra)
53    }
54}
55
56/// One kind of on-chip memory a part carries.
57#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
58pub struct Memory {
59    pub kind: MemoryKind,
60    /// How many the part carries.
61    pub count: Fact<u64>,
62    /// Storage bits in one, parity bits included: a 72-bit word holds nine bytes.
63    pub bits: Fact<u64>,
64    /// Bits one can read in a cycle, over all of its read ports.
65    pub read_bits_per_cycle: Fact<u64>,
66    /// Whether the configuration bitstream sets its contents.
67    pub preloaded: Fact<bool>,
68}
69
70/// What a programmable part holds.
71#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
72pub struct PartResources {
73    /// The device, as its vendor's tools name it without package or speed grade.
74    pub part: &'static str,
75    pub logic_unit: LogicUnit,
76    pub logic: Fact<u64>,
77    pub flops: Fact<u64>,
78    pub dsp: Fact<u64>,
79    /// The two's-complement operand widths one DSP block multiplies, in bits.
80    pub multiplier: Fact<(u32, u32)>,
81    pub memories: &'static [Memory],
82    pub sources: &'static [&'static str],
83}
84
85impl PartResources {
86    /// Every part the compiler knows.
87    pub fn catalog() -> Vec<PartResources> {
88        vec![PartResources::xc7a100t(), PartResources::xc7a200t(), PartResources::xczu7ev(), PartResources::sx2800()]
89    }
90
91    pub fn by_name(part: &str) -> Option<PartResources> {
92        PartResources::catalog().into_iter().find(|p| p.part == part)
93    }
94
95    /// The Artix-7 100T: Board B's Nexys A7-100T.
96    pub fn xc7a100t() -> PartResources {
97        PartResources {
98            part: "xc7a100t",
99            logic_unit: LogicUnit::Lut6,
100            logic: Fact::derived(
101                63_400,
102                "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A100T \"Slices 15,850\"; note 1 \"Each 7 series \
103                 FPGA slice contains four LUTs and eight flip-flops\": 15,850 × 4",
104            ),
105            flops: Fact::derived(
106                126_800,
107                "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A100T \"Slices 15,850\"; note 1 \"four LUTs and \
108                 eight flip-flops\": 15,850 × 8",
109            ),
110            dsp: Fact::published(240, "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A100T \"DSP48E1 Slices 240\""),
111            multiplier: Fact::published((25, 18), "UG479 (v1.10) 7 Series DSP48E1 Slice: \"25 × 18 two's-complement multiplier\""),
112            memories: XC7A100T_MEMORIES,
113            sources: XC7_SOURCES,
114        }
115    }
116
117    /// The Artix-7 200T: Board A's Nexys Video.
118    pub fn xc7a200t() -> PartResources {
119        PartResources {
120            part: "xc7a200t",
121            logic_unit: LogicUnit::Lut6,
122            logic: Fact::derived(
123                134_600,
124                "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A200T \"Slices 33,650\"; note 1 \"Each 7 series \
125                 FPGA slice contains four LUTs and eight flip-flops\": 33,650 × 4",
126            ),
127            flops: Fact::derived(
128                269_200,
129                "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A200T \"Slices 33,650\"; note 1 \"four LUTs and \
130                 eight flip-flops\": 33,650 × 8",
131            ),
132            dsp: Fact::published(740, "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A200T \"DSP48E1 Slices 740\""),
133            multiplier: Fact::published((25, 18), "UG479 (v1.10) 7 Series DSP48E1 Slice: \"25 × 18 two's-complement multiplier\""),
134            memories: XC7A200T_MEMORIES,
135            sources: XC7_SOURCES,
136        }
137    }
138
139    /// The Zynq UltraScale+ ZU7EV: the ZCU104's programmable logic.
140    pub fn xczu7ev() -> PartResources {
141        PartResources {
142            part: "xczu7ev",
143            logic_unit: LogicUnit::Lut6,
144            logic: Fact::published(
145                230_400,
146                "DS891 (v1.11.1) Zynq UltraScale+ MPSoC Data Sheet: Overview, Table 5: EV Device Feature Summary, ZU7EV \"CLB LUTs \
147                 230,400\"",
148            ),
149            flops: Fact::published(
150                460_800,
151                "DS891 (v1.11.1) Zynq UltraScale+ MPSoC Data Sheet: Overview, Table 5: EV Device Feature Summary, ZU7EV \"CLB \
152                 Flip-Flops 460,800\"",
153            ),
154            dsp: Fact::published(
155                1_728,
156                "DS891 (v1.11.1) Zynq UltraScale+ MPSoC Data Sheet: Overview, Table 5: EV Device Feature Summary, ZU7EV \"DSP Slices \
157                 1,728\"",
158            ),
159            multiplier: Fact::published(
160                (27, 18),
161                "UG579 (v1.7) UltraScale Architecture DSP Slice: \"27 × 18 two's complement multiplier with dynamic bypass\"",
162            ),
163            memories: XCZU7EV_MEMORIES,
164            sources: ZYNQ_US_PLUS_SOURCES,
165        }
166    }
167
168    /// The Stratix 10 SX 2800: the Intel PAC D5005's 1SX280HN2F43E2VG. The device overview counts its logic in
169    /// thousands of logic elements; the product table prints its ALMs and their registers.
170    pub fn sx2800() -> PartResources {
171        PartResources {
172            part: "1sx280",
173            logic_unit: LogicUnit::Alm,
174            logic: Fact::published(
175                933_120,
176                "GEN-1023-1.0 (May 2015) Stratix 10 Product Table: GX 2800 / SX 2800 \"Adaptive logic modules (ALMs) 933,120\"",
177            ),
178            flops: Fact::published(
179                3_732_480,
180                "GEN-1023-1.0 (May 2015) Stratix 10 Product Table: GX 2800 / SX 2800 \"ALM registers 3,732,480\"; S10-OVERVIEW \
181                 (2020.09.28) Intel Stratix 10 GX/SX Device Overview: \"4 Registers per ALM\"",
182            ),
183            dsp: Fact::published(
184                5_760,
185                "GEN-1023-1.0 (May 2015) Stratix 10 Product Table: GX 2800 / SX 2800 \"Variable-precision digital signal processing \
186                 (DSP) blocks 5,760\", \"18 x 19 multipliers 11,520\"",
187            ),
188            multiplier: Fact::published(
189                (18, 19),
190                "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview: \"Each DSP block can be independently configured \
191                 at compile time as either dual 18x19 or a single 27x27 multiply accumulate\"; the array multiplies on the two \
192                 18x19 halves (Table 4: \"18x19 Multipliers 11,520\", note 2: \"The number of 27x27 multipliers is one-half the \
193                 number of 18x19 multipliers\")",
194            ),
195            memories: SX2800_MEMORIES,
196            sources: S10_SOURCES,
197        }
198    }
199
200    /// The memory of `kind` the part carries.
201    pub fn memory(&self, kind: MemoryKind) -> Option<&'static Memory> {
202        self.memories.iter().find(|m| m.kind == kind)
203    }
204
205    /// Bytes the part's dedicated memory blocks store, all of their bits counted.
206    pub fn block_bytes(&self) -> u64 {
207        self.memories.iter().filter(|m| m.kind.is_block()).map(|m| m.count.value * m.bits.value / 8).sum()
208    }
209
210    /// Bytes the part's dedicated memory blocks read in one cycle, every read port busy.
211    pub fn block_read_bytes_per_cycle(&self) -> u64 {
212        self.memories
213            .iter()
214            .filter(|m| m.kind.is_block())
215            .map(|m| m.count.value * m.read_bits_per_cycle.value / 8)
216            .sum()
217    }
218
219    /// Every fact the part carries, with the path a lock names it by.
220    pub fn facts(&self) -> Vec<(String, Provenance, &'static str)> {
221        let mut out = vec![
222            ("logic".to_string(), self.logic.provenance, self.logic.cite),
223            ("flops".to_string(), self.flops.provenance, self.flops.cite),
224            ("dsp".to_string(), self.dsp.provenance, self.dsp.cite),
225            ("multiplier".to_string(), self.multiplier.provenance, self.multiplier.cite),
226        ];
227        for m in self.memories {
228            let at = |field: &str| format!("memory.{:?}.{field}", m.kind);
229            out.push((at("count"), m.count.provenance, m.count.cite));
230            out.push((at("bits"), m.bits.provenance, m.bits.cite));
231            out.push((at("read_bits_per_cycle"), m.read_bits_per_cycle.provenance, m.read_bits_per_cycle.cite));
232            out.push((at("preloaded"), m.preloaded.provenance, m.preloaded.cite));
233        }
234        out
235    }
236}
237
238const XC7_SOURCES: &[&str] = &[DS180, UG473, UG474, UG479];
239const S10_SOURCES: &[&str] = &[S10_OVERVIEW, S10_PRODUCT_TABLE];
240const ZYNQ_US_PLUS_SOURCES: &[&str] = &[DS891, UG573, UG574, UG579];
241
242const RAMB36E1_BITS: Fact<u64> = Fact::derived(
243    36_864,
244    "UG473 (v1.14) 7 Series FPGAs Memory Resources: a 36 Kb block RAM is \"32K x 1, 16K x 2, 8K x 4, 4K x 9, 2K x 18, 1K x 36, \
245     or 512 x 72 in simple dual-port mode\": 512 × 72",
246);
247const RAMB36E1_READ: Fact<u64> = Fact::derived(
248    72,
249    "UG473 (v1.14) 7 Series FPGAs Memory Resources: \"512 x 72 in simple dual-port mode\", or two 1K x 36 ports in true \
250     dual-port mode: 72 bits a cycle either way",
251);
252const RAMB36E1_PRELOADED: Fact<bool> = Fact::published(
253    true,
254    "UG473 (v1.14) 7 Series FPGAs Memory Resources: \"memory content can be initialized or cleared by the configuration \
255     bitstream\"",
256);
257const XC7_LUT_BITS: Fact<u64> = Fact::published(
258    64,
259    "UG474 (v1.8) 7 Series FPGAs Configurable Logic Block: SLICEMs \"can also use their LUTs as distributed 64-bit RAM\"",
260);
261const XC7_LUT_READ: Fact<u64> = Fact::derived(
262    1,
263    "UG474 (v1.8) 7 Series FPGAs Configurable Logic Block: a LUT of \"distributed 64-bit RAM\" read as 64 x 1 is one bit a \
264     cycle per read port",
265);
266const XC7_LUT_PRELOADED: Fact<bool> = Fact::derived(
267    true,
268    "UG474 (v1.8) 7 Series FPGAs Configurable Logic Block: distributed RAM is the LUT's own 64 configuration bits, which the \
269     bitstream writes",
270);
271
272const XC7A100T_MEMORIES: &[Memory] = &[
273    Memory {
274        kind: MemoryKind::LutRam,
275        count: Fact::derived(
276            19_008,
277            "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A100T \"Max Distributed RAM (Kb) 1,188\": 1,188 × 1024 \
278             / 64 bits a LUT",
279        ),
280        bits: XC7_LUT_BITS,
281        read_bits_per_cycle: XC7_LUT_READ,
282        preloaded: XC7_LUT_PRELOADED,
283    },
284    Memory {
285        kind: MemoryKind::Block36,
286        count: Fact::published(
287            135,
288            "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A100T \"Block RAM Blocks 18 Kb 270, 36 Kb 135\"",
289        ),
290        bits: RAMB36E1_BITS,
291        read_bits_per_cycle: RAMB36E1_READ,
292        preloaded: RAMB36E1_PRELOADED,
293    },
294];
295
296const XC7A200T_MEMORIES: &[Memory] = &[
297    Memory {
298        kind: MemoryKind::LutRam,
299        count: Fact::derived(
300            46_208,
301            "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A200T \"Max Distributed RAM (Kb) 2,888\": 2,888 × 1024 \
302             / 64 bits a LUT",
303        ),
304        bits: XC7_LUT_BITS,
305        read_bits_per_cycle: XC7_LUT_READ,
306        preloaded: XC7_LUT_PRELOADED,
307    },
308    Memory {
309        kind: MemoryKind::Block36,
310        count: Fact::published(
311            365,
312            "DS180 (v1.17) 7 Series FPGAs Data Sheet: Overview, Table 2: XC7A200T \"Block RAM Blocks 18 Kb 730, 36 Kb 365\"",
313        ),
314        bits: RAMB36E1_BITS,
315        read_bits_per_cycle: RAMB36E1_READ,
316        preloaded: RAMB36E1_PRELOADED,
317    },
318];
319
320const XCZU7EV_MEMORIES: &[Memory] = &[
321    Memory {
322        kind: MemoryKind::LutRam,
323        count: Fact::derived(
324            102_400,
325            "DS891 (v1.11.1) Zynq UltraScale+ MPSoC Data Sheet: Overview, Table 5: ZU7EV \"Distributed RAM (Mb) 6.2\", an upper \
326             bound from the top of its rounding: 6.25 × 2^20 / 64 bits a LUT (UG574 (v1.5) UltraScale Architecture Configurable \
327             Logic Block: \"distributed 64-bit RAM\")",
328        ),
329        bits: Fact::published(
330            64,
331            "UG574 (v1.5) UltraScale Architecture Configurable Logic Block: \"A SLICEM (memory) can also use the LUTs as \
332             distributed 64-bit RAM\"",
333        ),
334        read_bits_per_cycle: Fact::derived(
335            1,
336            "UG574 (v1.5) UltraScale Architecture Configurable Logic Block: \"The LUT can be configured as either a 64 x 1 or 32 \
337             x 2 memory\": one bit a cycle per read port at full depth",
338        ),
339        preloaded: Fact::derived(
340            true,
341            "UG574 (v1.5) UltraScale Architecture Configurable Logic Block: distributed RAM is the LUT's own 64 configuration bits, \
342             which the bitstream writes",
343        ),
344    },
345    Memory {
346        kind: MemoryKind::Block36,
347        count: Fact::published(
348            312,
349            "DS891 (v1.11.1) Zynq UltraScale+ MPSoC Data Sheet: Overview, Table 5: EV Device Feature Summary, ZU7EV \"Block RAM \
350             Blocks 312\"",
351        ),
352        bits: Fact::derived(
353            36_864,
354            "UG573 (v1.9) UltraScale Architecture Memory Resources: \"a 36 Kb block RAM can additionally be configured with a port \
355             width of 512 x 72 bits\": 512 × 72",
356        ),
357        read_bits_per_cycle: Fact::derived(
358            72,
359            "UG573 (v1.9) UltraScale Architecture Memory Resources: \"512 x 72 bits (when used as simple dual-port (SDP) memory)\", \
360             or two 1K x 36 read ports in true dual-port mode: 72 bits a cycle either way",
361        ),
362        preloaded: Fact::published(
363            true,
364            "UG573 (v1.9) UltraScale Architecture Memory Resources: \"The memory content can be initialized or cleared by the \
365             configuration bitstream\"",
366        ),
367    },
368    Memory {
369        kind: MemoryKind::Ultra,
370        count: Fact::published(
371            96,
372            "DS891 (v1.11.1) Zynq UltraScale+ MPSoC Data Sheet: Overview, Table 5: EV Device Feature Summary, ZU7EV \"UltraRAM \
373             Blocks 96\"",
374        ),
375        bits: Fact::derived(
376            294_912,
377            "UG573 (v1.9) UltraScale Architecture Memory Resources: \"Each UltraRAM block can store up to 288K bits of data and is \
378             configured as a 4K x 72 memory block\": 4096 × 72",
379        ),
380        read_bits_per_cycle: Fact::derived(
381            144,
382            "UG573 (v1.9) UltraScale Architecture Memory Resources: \"UltraRAM has two ports, both of which address all 4K x 72 \
383             bits. Each port can independently perform either one read or one write operation per clock cycle\": 2 × 72",
384        ),
385        preloaded: Fact::published(
386            false,
387            "UG573 (v1.9) UltraScale Architecture Memory Resources: \"There is no user defined INIT attribute and therefore the \
388             content of the SRAM array cannot be initialized to user defined values\"",
389        ),
390    },
391];
392
393const SX2800_MEMORIES: &[Memory] = &[
394    Memory {
395        kind: MemoryKind::Mlab,
396        count: Fact::published(
397            23_796,
398            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview, Table 4: GX 2800/ SX 2800 \"MLAB Counts 23,796\"",
399        ),
400        bits: Fact::published(
401            640,
402            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview: \"two types of embedded memory blocks: M20K (20 \
403             Kb) and MLAB (640 bit)\"",
404        ),
405        read_bits_per_cycle: Fact::derived(
406            20,
407            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview, Table 10: MLAB \"32 x 20\", its widest port",
408        ),
409        preloaded: Fact::derived(
410            true,
411            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview: \"Both M20K and MLAB embedded memory blocks can be \
412             configured as a single-port or dual-port RAM, FIFO, ROM, or shift register\"; a ROM's contents come from the \
413             bitstream",
414        ),
415    },
416    Memory {
417        kind: MemoryKind::M20k,
418        count: Fact::published(
419            11_721,
420            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview, Table 4: GX 2800/ SX 2800 \"M20K Blocks 11,721\", \
421             \"M20K Mbits 229\"",
422        ),
423        bits: Fact::derived(
424            20_480,
425            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview, Table 10: M20K (20 Kb) \"512 x 40 (or x32)\": 512 \
426             × 40",
427        ),
428        read_bits_per_cycle: Fact::derived(
429            40,
430            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview, Table 10: M20K \"2K x 10 (or x8)\", \"1K x 20 (or \
431             x16)\", \"512 x 40 (or x32)\": 40 bits a cycle at its widest",
432        ),
433        preloaded: Fact::derived(
434            true,
435            "S10-OVERVIEW (2020.09.28) Intel Stratix 10 GX/SX Device Overview: \"Both M20K and MLAB embedded memory blocks can be \
436             configured as a single-port or dual-port RAM, FIFO, ROM, or shift register\"; a ROM's contents come from the \
437             bitstream",
438        ),
439    },
440];