Skip to main content

rhdl_fpga_planner/
resources.rs

1//! What the chip has: the U280's resources, the budget the percentages
2//! mean, and resource arithmetic ([`Cost`], [`Budget`], [`Resource`]).
3
4use crate::*;
5
6/// Physical CLB LUTs of the XCU280.
7pub const U280_LUT: u32 = 1_303_680;
8/// Physical DSP48E2 blocks of the XCU280.
9pub const U280_DSP: u32 = 9_024;
10/// Physical BRAM36 blocks of the XCU280.
11pub const U280_BRAM36: u32 = 2_016;
12/// Physical URAM blocks of the XCU280.
13pub const U280_URAM: u32 = 960;
14/// BRAM36 that the 100 % BRAM setting means: the U280 shell's dynamic
15/// region holds 3,552 BRAM18 sites (1,776 BRAM36), not the device's 2,016.
16/// DSig's build v4 failed placement asking for 4,033 BRAM18 there.
17pub const BUDGET_BRAM36: u32 = 1_776;
18/// LUT budget that the 100 % setting means: 420k for the signature
19/// engines plus the SHA-512 line's default 65k, which now comes out of the
20/// budget like everything else ([`SHA_DEFAULT_LUT`]).
21pub const BASE_LUT: u32 = 485_000;
22/// DSP budget that the 100 % setting means: 4,500 for the engines plus the
23/// SHA-512 line's 20.
24pub const BASE_DSP: u32 = 4_520;
25/// The SHA-512 line's default LUTs (13.4 % of [`BASE_LUT`]): with it, the
26/// default chip gives the engines the same 420k they had when the line sat
27/// outside the budget.
28pub const SHA_DEFAULT_LUT: u32 = 65_000;
29/// DSig's default batch: one-time keys certified by one Ed25519 signature
30/// (used when the DSig module does not report its own, [`Plan::batch`]).
31pub const DSIG_BATCH: f64 = 128.0;
32/// The planner's name for SHA-512 compression throughput (blocks/s).
33pub const SHA512_BLOCKS: &str = "sha512 blocks";
34
35/// Resources of one build.
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct Cost {
38    /// CLB LUTs.
39    pub lut: u32,
40    /// DSP48E2 blocks.
41    pub dsp: u32,
42    /// BRAM36 blocks.
43    pub bram36: u32,
44    /// URAM blocks.
45    pub uram: u32,
46}
47
48/// Arithmetic and limit checks on resource counts.
49impl Cost {
50    /// `self + n * other`.
51    pub fn add(self, other: Cost, n: u32) -> Cost {
52        Cost {
53            lut: self.lut + n * other.lut,
54            dsp: self.dsp + n * other.dsp,
55            bram36: self.bram36 + n * other.bram36,
56            uram: self.uram + n * other.uram,
57        }
58    }
59    /// Every resource within `share`.
60    pub fn fits(&self, share: &Share) -> bool {
61        self.lut <= share.lut && self.dsp <= share.dsp && self.bram36 <= share.bram36 && self.uram <= share.uram
62    }
63    /// The resource closest to its limit in `share`.
64    pub fn limit(&self, share: &Share) -> &'static str {
65        let ratios = [
66            (self.lut as f64 / share.lut.max(1) as f64, "LUT"),
67            (self.dsp as f64 / share.dsp.max(1) as f64, "DSP"),
68            (self.bram36 as f64 / share.bram36.max(1) as f64, "BRAM"),
69            (self.uram as f64 / share.uram.max(1) as f64, "URAM"),
70        ];
71        ratios.iter().fold((0.0, "none"), |a, b| if b.0 > a.0 { *b } else { a }).1
72    }
73}
74
75/// The resources one component may use.
76pub type Share = Cost;
77
78/// The global resource controls.
79#[derive(Clone, Copy, Debug, PartialEq)]
80pub struct Budget {
81    /// LUTs as a percentage of [`BASE_LUT`] (0..200).
82    pub lut_percent: f64,
83    /// DSPs as a percentage of [`BASE_DSP`] (0..200).
84    pub dsp_percent: f64,
85    /// BRAM36 as a percentage of the dynamic region's [`BUDGET_BRAM36`]
86    /// (0..100).
87    pub bram_percent: f64,
88    /// URAM as a percentage of the device's [`U280_URAM`] (0..100).
89    pub uram_percent: f64,
90}
91
92/// 100 % of everything: 485k LUTs, 4,520 DSPs, the dynamic region's BRAM and
93/// all URAM.
94impl Default for Budget {
95    fn default() -> Self {
96        Budget { lut_percent: 100.0, dsp_percent: 100.0, bram_percent: 100.0, uram_percent: 100.0 }
97    }
98}
99
100/// Absolute resource counts from the percentages.
101impl Budget {
102    /// LUT budget, never above the device.
103    pub fn luts(&self) -> u32 {
104        ((BASE_LUT as f64 * self.lut_percent / 100.0) as u32).min(U280_LUT)
105    }
106    /// DSP budget, never above the device.
107    pub fn dsps(&self) -> u32 {
108        ((BASE_DSP as f64 * self.dsp_percent / 100.0) as u32).min(U280_DSP)
109    }
110    /// BRAM36 budget.
111    pub fn brams(&self) -> u32 {
112        (BUDGET_BRAM36 as f64 * self.bram_percent.clamp(0.0, 100.0) / 100.0) as u32
113    }
114    /// URAM budget.
115    pub fn urams(&self) -> u32 {
116        (U280_URAM as f64 * self.uram_percent.clamp(0.0, 100.0) / 100.0) as u32
117    }
118    /// The chip limit: every resource of the budget. Fixed allowances (the
119    /// SHA-512 line) come out of it like any module's build.
120    pub fn chip(&self) -> Share {
121        Share { lut: self.luts(), dsp: self.dsps(), bram36: self.brams(), uram: self.urams() }
122    }
123    /// What the modules with powers divide: the budget minus the fixed
124    /// allowances of `slots` (the SHA-512 line's LUT split).
125    pub fn rest(&self, slots: &[Slot]) -> Share {
126        self.rest_with(&allowances(slots))
127    }
128    /// [`rest`](Self::rest) with the fixed allowances given per slot.
129    pub fn rest_with(&self, allow: &[Option<Share>]) -> Share {
130        let fixed = allow.iter().flatten().fold(Cost::default(), |a, f| a.add(*f, 1));
131        Share {
132            lut: self.luts().saturating_sub(fixed.lut),
133            dsp: self.dsps().saturating_sub(fixed.dsp),
134            bram36: self.brams().saturating_sub(fixed.bram36),
135            uram: self.urams().saturating_sub(fixed.uram),
136        }
137    }
138}
139
140/// The four resources, in the order of [`Slot::limits`].
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142pub enum Resource {
143    /// CLB LUTs.
144    Lut,
145    /// DSP48E2 blocks.
146    Dsp,
147    /// BRAM36 blocks.
148    Bram,
149    /// URAM blocks.
150    Uram,
151}
152
153impl Resource {
154    /// All four, in [`Slot::limits`] order.
155    pub const ALL: [Resource; 4] = [Resource::Lut, Resource::Dsp, Resource::Bram, Resource::Uram];
156    /// Display name.
157    pub fn name(self) -> &'static str {
158        match self {
159            Resource::Lut => "LUT",
160            Resource::Dsp => "DSP",
161            Resource::Bram => "BRAM",
162            Resource::Uram => "URAM",
163        }
164    }
165    /// This resource's count in `c`.
166    pub fn of(self, c: &Cost) -> u32 {
167        match self {
168            Resource::Lut => c.lut,
169            Resource::Dsp => c.dsp,
170            Resource::Bram => c.bram36,
171            Resource::Uram => c.uram,
172        }
173    }
174    /// `c` with this resource set to `n`.
175    pub fn with(self, c: Cost, n: u32) -> Cost {
176        match self {
177            Resource::Lut => Cost { lut: n, ..c },
178            Resource::Dsp => Cost { dsp: n, ..c },
179            Resource::Bram => Cost { bram36: n, ..c },
180            Resource::Uram => Cost { uram: n, ..c },
181        }
182    }
183}