Skip to main content

logicaffeine_compile/codegen_sva/
floorplan.rs

1//! Floorplans read off a fitted build: a Logic Lock region for each instance named, a square beside the site its clock
2//! comes from, as small as what the instance needs at the parent region's density allows, inside the parent region.
3//!
4//! The build says everything the floorplan needs. Its clock table names each clock's target cell, and the fitter's
5//! fast-signal table the site of each net it put on a fast network: a clock's site is the fast signal sharing its
6//! target's deepest hierarchy (an EMIF's user clock leaves its IO tile's control, the net beside it). An instance's clock
7//! is the one its timing paths run on, both ends inside it. Its need is the fitter's own entity table (ALMs needed,
8//! M20Ks), and the parent region's density its capacity over its sites. The region is then the square that holds the
9//! need at a fill (half by default, room for the router), every site of it in the parent region, the nearest such square
10//! to the clock's site. Quartus takes it as a `PLACE_REGION` with `CORE_ONLY_PLACE_REGION` on and
11//! `RESERVE_PLACE_REGION` off, so the rest of the design may share the sites.
12
13use std::fmt::Write as _;
14
15use serde::{Deserialize, Serialize};
16
17use super::netlist::Graph;
18use super::quartus::{headed_table, number, parse_region_usage, RegionUsage};
19
20/// A rectangle of sites, both corners inclusive.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct Rect {
23    pub x0: u32,
24    pub y0: u32,
25    pub x1: u32,
26    pub y1: u32,
27}
28
29impl Rect {
30    pub fn sites(&self) -> u64 {
31        u64::from(self.x1 - self.x0 + 1) * u64::from(self.y1 - self.y0 + 1)
32    }
33
34    pub fn contains(&self, x: u32, y: u32) -> bool {
35        (self.x0..=self.x1).contains(&x) && (self.y0..=self.y1).contains(&y)
36    }
37
38    /// The Chebyshev distance from a site to the nearest site of the rectangle, 0 inside it.
39    pub fn distance(&self, (x, y): (u32, u32)) -> u32 {
40        let gap = |v: u32, lo: u32, hi: u32| if v < lo { lo - v } else { v.saturating_sub(hi) };
41        gap(x, self.x0, self.x1).max(gap(y, self.y0, self.y1))
42    }
43}
44
45/// The site of a location the fitter names, `TILECTRL_X233_Y36_N2` its (233, 36).
46pub fn site(location: &str) -> Option<(u32, u32)> {
47    let field = |key: char| -> Option<u32> {
48        let at = location.split('_').find(|p| p.starts_with(key) && p.len() > 1 && p[1..].bytes().all(|b| b.is_ascii_digit()))?;
49        at[1..].parse().ok()
50    };
51    Some((field('X')?, field('Y')?))
52}
53
54/// Every net of the fit report's `Global & Other Fast Signals Summary` with the site it is driven from.
55pub fn fast_signal_sites(place: &str) -> Vec<(String, (u32, u32))> {
56    let Some((header, rows)) = headed_table(place, "Global & Other Fast Signals Summary") else { return Vec::new() };
57    let (Some(name), Some(location)) = (header.iter().position(|h| *h == "Name"), header.iter().position(|h| *h == "Location")) else {
58        return Vec::new();
59    };
60    rows.iter().filter_map(|r| Some((r.get(name)?.to_string(), site(r.get(location)?)?))).collect()
61}
62
63/// Every clock of the timing analyzer's `Clocks` table with its target cells.
64pub fn clock_targets(clocks: &str) -> Vec<(String, Vec<String>)> {
65    let Some((header, rows)) = headed_table(clocks, "Clocks") else { return Vec::new() };
66    let (Some(name), Some(targets)) = (header.iter().position(|h| *h == "Clock Name"), header.iter().position(|h| *h == "Targets")) else {
67        return Vec::new();
68    };
69    rows.iter()
70        .filter_map(|r| {
71            let list = r.get(targets)?.trim().trim_start_matches('{').trim_end_matches('}');
72            Some((r.get(name)?.to_string(), list.split_whitespace().map(str::to_string).collect()))
73        })
74        .collect()
75}
76
77/// How many leading hierarchy levels two names share.
78fn shared_levels(a: &str, b: &str) -> usize {
79    a.split('|').zip(b.split('|')).take_while(|(x, y)| x == y).count()
80}
81
82/// The site a clock comes from: the fast signal sharing one of its targets' deepest hierarchy, or none when no fast
83/// signal shares any level or two share the deepest.
84pub fn clock_site(targets: &[String], fast: &[(String, (u32, u32))]) -> Option<(u32, u32)> {
85    let depth = |net: &str| targets.iter().map(|t| shared_levels(t, net)).max().unwrap_or(0);
86    let best = fast.iter().map(|(n, _)| depth(n)).max().filter(|&d| d > 0)?;
87    let mut at = fast.iter().filter(|(n, _)| depth(n) == best);
88    let (_, site) = at.next()?;
89    at.next().is_none().then_some(*site)
90}
91
92/// The clock an instance runs on: the latch clock of the most setup paths in the timing report whose both ends lie
93/// inside it, none when no path does or two clocks tie.
94pub fn instance_clock(setup: &str, instance: &str) -> Option<String> {
95    let inside = |node: &str| node.strip_prefix(instance).is_some_and(|rest| rest.starts_with('|'));
96    let mut counts: Vec<(String, usize)> = Vec::new();
97    for line in setup.lines() {
98        let Some(c) = cells_of(line) else { continue };
99        let [slack, from, to, _, latch, ..] = c.as_slice() else { continue };
100        if slack.parse::<f64>().is_err() || !inside(from) || !inside(to) {
101            continue;
102        }
103        match counts.iter_mut().find(|(clock, _)| clock == latch) {
104            Some((_, n)) => *n += 1,
105            None => counts.push((latch.to_string(), 1)),
106        }
107    }
108    counts.sort_by(|a, b| b.1.cmp(&a.1));
109    match counts.as_slice() {
110        [(clock, _)] => Some(clock.clone()),
111        [(clock, n), (_, m), ..] if n > m => Some(clock.clone()),
112        _ => None,
113    }
114}
115
116fn cells_of(line: &str) -> Option<Vec<&str>> {
117    let t = line.trim_end();
118    (t.starts_with(';') && t.ends_with(';') && t.len() > 1).then(|| t[1..t.len() - 1].split(';').map(str::trim).collect())
119}
120
121/// What an instance needs of a region: its ALMs needed and its M20Ks.
122#[derive(Clone, Copy, Debug, PartialEq)]
123pub struct Need {
124    pub alms: f64,
125    pub m20k: u64,
126}
127
128/// An instance's need as the fit report's `Fitter Resource Utilization by Entity` states it (its totals, itself and
129/// everything under it).
130pub fn instance_need(place: &str, instance: &str) -> Option<Need> {
131    let (header, rows) = headed_table(place, "Fitter Resource Utilization by Entity")?;
132    let column = |name: &str| header.iter().position(|h| *h == name);
133    let (alms, m20k, path) = (column("ALMs needed [=A-B+C]")?, column("M20Ks")?, column("Full Hierarchy Name")?);
134    let row = rows.iter().find(|r| r.get(path) == Some(&instance))?;
135    let total = |cell: &str| cell.split('(').next().map(str::trim).map(str::to_string);
136    Some(Need { alms: total(row.get(alms)?)?.parse().ok()?, m20k: number(&total(row.get(m20k)?)?)? })
137}
138
139/// A region's resources a site: its ALMs and its M20Ks over its sites.
140#[derive(Clone, Copy, Debug, PartialEq)]
141pub struct Density {
142    pub alms: f64,
143    pub m20k: f64,
144}
145
146impl Density {
147    /// The density of a region the fitter reported on: what it holds over its rectangles' sites.
148    pub fn of(region: &RegionUsage) -> Option<Density> {
149        let sites: u64 = region.placement.iter().map(|&((x0, y0), (x1, y1))| Rect { x0, y0, x1, y1 }.sites()).sum();
150        let m20k = region.m20k?.total;
151        (sites > 0).then(|| Density { alms: region.alms as f64 / sites as f64, m20k: m20k as f64 / sites as f64 })
152    }
153}
154
155/// The parent region a build's floorplan lies in: the rectangles of its one Logic Lock region (a partial
156/// reconfiguration build's persona), with that region's density.
157pub fn parent_region(place: &str) -> Option<(Vec<Rect>, Density)> {
158    let regions = parse_region_usage(place);
159    let [region] = regions.as_slice() else { return None };
160    let rects = region.placement.iter().map(|&((x0, y0), (x1, y1))| Rect { x0, y0, x1, y1 }).collect();
161    Some((rects, Density::of(region)?))
162}
163
164/// The device's sites: the route region of the fit report's `Logic Lock Region Constraints`, the whole core a flat build
165/// may place in. It is a row's last cell: the place region before it separates its rectangles with the table's own `;`.
166pub fn device(place: &str) -> Option<Rect> {
167    let (header, rows) = headed_table(place, "Logic Lock Region Constraints")?;
168    if header.last() != Some(&"Route Region Constraint") {
169        return None;
170    }
171    let numbers: Vec<u32> =
172        rows.first()?.last()?.split(|c: char| !c.is_ascii_digit()).filter(|t| !t.is_empty()).filter_map(|t| t.parse().ok()).collect();
173    match numbers.as_slice() {
174        [x0, y0, x1, y1] => Some(Rect { x0: *x0, y0: *y0, x1: *x1, y1: *y1 }),
175        _ => None,
176    }
177}
178
179/// The square that holds `need` at `fill` of `density`, every site of it in `parent`, nearest `anchor`: the least side
180/// whose sites hold the need, then of its placements the one nearest the anchor (Chebyshev), then the one whose centre
181/// is nearest, then the lowest. None when no placement of that side fits, or the density holds none of what is needed.
182pub fn region(anchor: (u32, u32), need: Need, density: Density, fill: f64, parent: &[Rect]) -> Option<Rect> {
183    if parent.is_empty() || !(fill > 0.0 && fill <= 1.0) || density.alms <= 0.0 || (need.m20k > 0 && density.m20k <= 0.0) {
184        return None;
185    }
186    let sites = (need.alms / (density.alms * fill)).max(need.m20k as f64 / (density.m20k * fill).max(f64::MIN_POSITIVE));
187    let side = (sites.sqrt().ceil() as u32).max(1);
188    let (bx0, by0) = (parent.iter().map(|r| r.x0).min()?, parent.iter().map(|r| r.y0).min()?);
189    let (bx1, by1) = (parent.iter().map(|r| r.x1).max()?, parent.iter().map(|r| r.y1).max()?);
190    let (w, h) = ((bx1 - bx0 + 1) as usize, (by1 - by0 + 1) as usize);
191    if (side as usize) > w || (side as usize) > h {
192        return None;
193    }
194    // Sites of the parent, summed: a square is inside it when its sum is its area.
195    let mut sum = vec![0u32; (w + 1) * (h + 1)];
196    for y in 0..h {
197        for x in 0..w {
198            let here = u32::from(parent.iter().any(|r| r.contains(bx0 + x as u32, by0 + y as u32)));
199            sum[(y + 1) * (w + 1) + x + 1] = here + sum[y * (w + 1) + x + 1] + sum[(y + 1) * (w + 1) + x] - sum[y * (w + 1) + x];
200        }
201    }
202    let s = side as usize;
203    let inside = |x: usize, y: usize| -> bool {
204        let at = |xx: usize, yy: usize| sum[yy * (w + 1) + xx];
205        at(x + s, y + s) + at(x, y) - at(x, y + s) - at(x + s, y) == side * side
206    };
207    let centre_gap = |r: &Rect| -> u64 {
208        let (cx, cy) = (u64::from(r.x0 + r.x1), u64::from(r.y0 + r.y1));
209        cx.abs_diff(2 * u64::from(anchor.0)) + cy.abs_diff(2 * u64::from(anchor.1))
210    };
211    let mut best: Option<(u32, u64, Rect)> = None;
212    for y in 0..=(h - s) {
213        for x in 0..=(w - s) {
214            if !inside(x, y) {
215                continue;
216            }
217            let r = Rect { x0: bx0 + x as u32, y0: by0 + y as u32, x1: bx0 + (x + s) as u32 - 1, y1: by0 + (y + s) as u32 - 1 };
218            let key = (r.distance(anchor), centre_gap(&r));
219            if best.as_ref().map_or(true, |(d, c, _)| key < (*d, *c)) {
220                best = Some((key.0, key.1, r));
221            }
222        }
223    }
224    best.map(|(_, _, r)| r)
225}
226
227/// One region: the instances it holds, the name Quartus gives it, its rectangle.
228#[derive(Clone, Debug, PartialEq, Eq)]
229pub struct Assignment {
230    pub instances: Vec<String>,
231    pub name: String,
232    pub rect: Rect,
233}
234
235/// The assignments as Quartus settings: each region a `PLACE_REGION` of every instance it holds, under its one name,
236/// core sites only, shared with the rest of the design.
237pub fn tcl(assignments: &[Assignment]) -> String {
238    let mut out = String::new();
239    for a in assignments {
240        for instance in &a.instances {
241            settings(&mut out, instance, &a.name, &[a.rect]);
242        }
243    }
244    out
245}
246
247/// One region's settings: a `PLACE_REGION` of its rectangles (`;` between them, as Quartus writes a region of several),
248/// its name, core sites only, shared with the rest of the design.
249fn settings(out: &mut String, instance: &str, name: &str, rects: &[Rect]) {
250    let place: Vec<String> = rects.iter().map(|r| format!("X{} Y{} X{} Y{}", r.x0, r.y0, r.x1, r.y1)).collect();
251    let _ = writeln!(out, "set_instance_assignment -name PLACE_REGION \"{}\" -to {instance}", place.join(";"));
252    let _ = writeln!(out, "set_instance_assignment -name REGION_NAME {name} -to {instance}");
253    let _ = writeln!(out, "set_instance_assignment -name CORE_ONLY_PLACE_REGION ON -to {instance}");
254    let _ = writeln!(out, "set_instance_assignment -name RESERVE_PLACE_REGION OFF -to {instance}");
255}
256
257/// What a tile holds, as the fitter counted it.
258#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
259pub struct Capacity {
260    pub alms: u64,
261    pub registers: u64,
262    pub m20k: u64,
263    pub dsp: u64,
264}
265
266/// The device cut into tiles of `width` columns by `height` rows from its corner, row by row, the last of each row and
267/// column clipped to the device.
268pub fn tiles(device: Rect, width: u32, height: u32) -> Vec<Rect> {
269    let mut out = Vec::new();
270    for y0 in (device.y0..=device.y1).step_by(height.max(1) as usize) {
271        for x0 in (device.x0..=device.x1).step_by(width.max(1) as usize) {
272            out.push(Rect { x0, y0, x1: (x0 + width - 1).min(device.x1), y1: (y0 + height - 1).min(device.y1) });
273        }
274    }
275    out
276}
277
278/// The geometry probe for `tiles`: a design of one register cell a tile (`c<k>`), and the Logic Lock region pinning each
279/// cell to its tile (`geo_<k>`), so a fit of it reports every tile's capacity in its region usage.
280pub fn probe(tiles: &[Rect]) -> (String, String) {
281    let n = tiles.len();
282    let mut v = String::from("module logos_geometry_cell(input clk, input d, output reg q);\n  always @(posedge clk) q <= d;\nendmodule\n\n");
283    writeln!(v, "module logos_geometry_probe(input clk, input [{}:0] d, output [{}:0] q);", n.max(1) - 1, n.max(1) - 1).unwrap();
284    for k in 0..n {
285        writeln!(v, "  logos_geometry_cell c{k}(.clk(clk), .d(d[{k}]), .q(q[{k}]));").unwrap();
286    }
287    v.push_str("endmodule\n");
288    let plan: Vec<Assignment> = tiles.iter().enumerate().map(|(k, &rect)| Assignment { instances: vec![format!("c{k}")], name: format!("geo_{k}"), rect }).collect();
289    (v, tcl(&plan))
290}
291
292/// The tiles a probe's fit found no flip-flop in: each region its log refuses as needing a flip-flop where there are
293/// none (`Deterministic overuse of FF resources in region (x0,y0) to (x1,y1)`), a tile of the device with no logic
294/// (hard IP, the core's edge), which the next probe leaves out and no floorplan places a region on.
295pub fn fabricless(log: &str) -> Vec<Rect> {
296    let mut out: Vec<Rect> = Vec::new();
297    for line in log.lines() {
298        let Some(at) = line.find("Deterministic overuse of FF resources in region ") else { continue };
299        let numbers: Vec<u32> = line[at..].split(|c: char| !c.is_ascii_digit()).filter(|t| !t.is_empty()).filter_map(|t| t.parse().ok()).collect();
300        if let [x0, y0, x1, y1] = numbers[..] {
301            let r = Rect { x0, y0, x1, y1 };
302            if !out.contains(&r) {
303                out.push(r);
304            }
305        }
306    }
307    out
308}
309
310/// The device's capacity map, as a probe's fit counted it: each tile and what it holds.
311#[derive(Clone, Debug, PartialEq)]
312pub struct Geometry {
313    pub tiles: Vec<(Rect, Capacity)>,
314}
315
316impl Geometry {
317    /// The map a probe's fit records: every `geo_<k>` region, one rectangle each, with its counts; none when a region
318    /// lacks its counts or no region is a probe's.
319    pub fn of(regions: &[RegionUsage]) -> Option<Geometry> {
320        let mut tiles = Vec::new();
321        for g in regions.iter().filter(|g| g.name.strip_prefix("geo_").is_some_and(|k| !k.is_empty() && k.bytes().all(|b| b.is_ascii_digit()))) {
322            let [((x0, y0), (x1, y1))] = g.placement[..] else { return None };
323            let capacity = Capacity { alms: g.alms, registers: g.registers?.total, m20k: g.m20k?.total, dsp: g.dsp?.total };
324            tiles.push((Rect { x0, y0, x1, y1 }, capacity));
325        }
326        (!tiles.is_empty()).then_some(Geometry { tiles })
327    }
328
329    /// What `rect` holds when it is a union of whole tiles, each tile's counts summed; none when it cuts a tile or reaches
330    /// past the map, a part of a tile being unmeasured.
331    pub fn capacity(&self, rect: Rect) -> Option<Capacity> {
332        let inside = |t: &Rect| t.x0 >= rect.x0 && t.x1 <= rect.x1 && t.y0 >= rect.y0 && t.y1 <= rect.y1;
333        let overlaps = |t: &Rect| t.x0 <= rect.x1 && rect.x0 <= t.x1 && t.y0 <= rect.y1 && rect.y0 <= t.y1;
334        if self.tiles.iter().any(|(t, _)| overlaps(t) && !inside(t)) {
335            return None;
336        }
337        let whole: Vec<&(Rect, Capacity)> = self.tiles.iter().filter(|(t, _)| inside(t)).collect();
338        if whole.iter().map(|(t, _)| t.sites()).sum::<u64>() != rect.sites() {
339            return None;
340        }
341        Some(whole.iter().fold(Capacity::default(), |c, (_, t)| Capacity {
342            alms: c.alms + t.alms,
343            registers: c.registers + t.registers,
344            m20k: c.m20k + t.m20k,
345            dsp: c.dsp + t.dsp,
346        }))
347    }
348}
349
350/// What a unit needs placed.
351#[derive(Clone, Copy, Debug, Default, PartialEq)]
352pub struct Needs {
353    pub alms: f64,
354    pub registers: f64,
355    pub m20k: u64,
356    pub dsp: u64,
357}
358
359/// A unit the floorplan places: the instances its one region holds, the region's name, what they need together.
360#[derive(Clone, Debug, PartialEq)]
361pub struct Unit {
362    pub instances: Vec<String>,
363    pub name: String,
364    pub needs: Needs,
365}
366
367impl Unit {
368    /// The name a unit's refusal gives it: its first instance, or its region's name when it holds none.
369    fn named(&self) -> String {
370        self.instances.first().cloned().unwrap_or_else(|| self.name.clone())
371    }
372}
373
374/// A bus the floorplan keeps short, weighted by its width in bits: between two units, by their index, or between a unit
375/// and a fixed site.
376#[derive(Clone, Copy, Debug, PartialEq)]
377pub enum Wire {
378    Units(usize, usize, u64),
379    Site(usize, (u32, u32), u64),
380}
381
382/// Why a floorplan places no region.
383#[derive(Clone, Debug, PartialEq, Eq)]
384pub enum PlaceError {
385    /// No rectangle the map measures holds this unit clear of the rest.
386    NoRegion(String),
387    /// The module has no instance of this unit.
388    Unknown(String),
389    /// One region name would serve both units.
390    SameName(String, String),
391    /// A unit of no instance, by its name.
392    Empty(String),
393    /// An instance two units hold.
394    Twice(String),
395}
396
397impl std::fmt::Display for PlaceError {
398    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399        match self {
400            PlaceError::NoRegion(unit) => write!(f, "{unit}: no free whole tiles hold it clear of the regions before it"),
401            PlaceError::Unknown(unit) => write!(f, "{unit}: the module has no such instance"),
402            PlaceError::SameName(a, b) => write!(f, "{a} and {b}: one region name for both"),
403            PlaceError::Empty(name) => write!(f, "{name}: a unit of no instance"),
404            PlaceError::Twice(instance) => write!(f, "{instance}: in two units"),
405        }
406    }
407}
408
409/// How much of a region a unit may take of each resource: of its logic (ALMs and registers), the rest room for the router
410/// and for the logic that shares the region; of its M20Ks and its DSP blocks, hard blocks that need no such room.
411#[derive(Clone, Copy, Debug, PartialEq)]
412pub struct Fill {
413    pub logic: f64,
414    pub m20k: f64,
415    pub dsp: f64,
416}
417
418impl Fill {
419    /// One share of every resource.
420    pub fn uniform(share: f64) -> Fill {
421        Fill { logic: share, m20k: share, dsp: share }
422    }
423}
424
425/// Whether a region of `capacity` holds a unit of `needs` at `fill`: each resource the unit needs within its share of the
426/// region's.
427pub fn holds(capacity: Capacity, needs: Needs, fill: Fill) -> bool {
428    needs.alms <= fill.logic * capacity.alms as f64
429        && needs.registers <= fill.logic * capacity.registers as f64
430        && needs.m20k as f64 <= fill.m20k * capacity.m20k as f64
431        && needs.dsp as f64 <= fill.dsp * capacity.dsp as f64
432}
433
434/// The map's tiles as a grid: the tiles' distinct columns and rows, each cell's capacity (none where no tile is), and
435/// the prefix sums of every count, so a block of whole tiles is summed at once.
436struct Grid {
437    xs: Vec<(u32, u32)>,
438    ys: Vec<(u32, u32)>,
439    sums: Vec<[u64; 5]>,
440}
441
442impl Grid {
443    fn of(geometry: &Geometry) -> Grid {
444        let mut xs: Vec<(u32, u32)> = geometry.tiles.iter().map(|(t, _)| (t.x0, t.x1)).collect();
445        let mut ys: Vec<(u32, u32)> = geometry.tiles.iter().map(|(t, _)| (t.y0, t.y1)).collect();
446        xs.sort_unstable();
447        xs.dedup();
448        ys.sort_unstable();
449        ys.dedup();
450        let (w, h) = (xs.len(), ys.len());
451        let mut sums = vec![[0u64; 5]; (w + 1) * (h + 1)];
452        let at = |i: usize, j: usize| j * (w + 1) + i;
453        for (t, c) in &geometry.tiles {
454            let (Ok(i), Ok(j)) = (xs.binary_search(&(t.x0, t.x1)), ys.binary_search(&(t.y0, t.y1))) else { continue };
455            sums[at(i + 1, j + 1)] = [c.alms, c.registers, c.m20k, c.dsp, 1];
456        }
457        for j in 1..=h {
458            for i in 1..=w {
459                for k in 0..5 {
460                    sums[at(i, j)][k] = sums[at(i, j)][k] + sums[at(i - 1, j)][k] + sums[at(i, j - 1)][k] - sums[at(i - 1, j - 1)][k];
461                }
462            }
463        }
464        Grid { xs, ys, sums }
465    }
466
467    /// The tiles from column `i0` to `i1` and row `j0` to `j1`: their rectangle and capacity, none if a cell has no
468    /// tile.
469    fn block(&self, i0: usize, i1: usize, j0: usize, j1: usize) -> Option<(Rect, Capacity)> {
470        let w = self.xs.len() + 1;
471        let s = |i: usize, j: usize| self.sums[j * w + i];
472        let k = |n: usize| s(i1 + 1, j1 + 1)[n] + s(i0, j0)[n] - s(i0, j1 + 1)[n] - s(i1 + 1, j0)[n];
473        if k(4) != ((i1 - i0 + 1) * (j1 - j0 + 1)) as u64 {
474            return None;
475        }
476        let rect = Rect { x0: self.xs[i0].0, y0: self.ys[j0].0, x1: self.xs[i1].1, y1: self.ys[j1].1 };
477        Some((rect, Capacity { alms: k(0), registers: k(1), m20k: k(2), dsp: k(3) }))
478    }
479}
480
481fn overlap(a: &Rect, b: &Rect) -> bool {
482    a.x0 <= b.x1 && b.x0 <= a.x1 && a.y0 <= b.y1 && b.y0 <= a.y1
483}
484
485/// Every rectangle of whole tiles of `geometry` that holds `needs` at `fill`, overlaps none of `blocked`, is no longer
486/// than `aspect` times its width in sites, and is least: no rectangle a tile row or column smaller inside it holds the
487/// needs too. In order of their top row, then left column, then size.
488pub fn candidates(geometry: &Geometry, needs: Needs, fill: Fill, aspect: f64, blocked: &[Rect]) -> Vec<Rect> {
489    let grid = Grid::of(geometry);
490    let (w, h) = (grid.xs.len(), grid.ys.len());
491    let holding = |i0: usize, i1: usize, j0: usize, j1: usize| grid.block(i0, i1, j0, j1).is_some_and(|(_, c)| holds(c, needs, fill));
492    let mut out = Vec::new();
493    for j0 in 0..h {
494        for i0 in 0..w {
495            for j1 in j0..h {
496                for i1 in i0..w {
497                    let Some((rect, capacity)) = grid.block(i0, i1, j0, j1) else { continue };
498                    if !holds(capacity, needs, fill) || blocked.iter().any(|b| overlap(&rect, b)) {
499                        continue;
500                    }
501                    let (long, short) = {
502                        let (x, y) = ((rect.x1 - rect.x0 + 1) as f64, (rect.y1 - rect.y0 + 1) as f64);
503                        (x.max(y), x.min(y))
504                    };
505                    if long > aspect * short {
506                        continue;
507                    }
508                    let smaller = (i1 > i0 && (holding(i0 + 1, i1, j0, j1) || holding(i0, i1 - 1, j0, j1)))
509                        || (j1 > j0 && (holding(i0, i1, j0 + 1, j1) || holding(i0, i1, j0, j1 - 1)));
510                    if !smaller {
511                        out.push(rect);
512                    }
513                }
514            }
515        }
516    }
517    out
518}
519
520/// The centre of `r`, in sites.
521fn centre(r: &Rect) -> (f64, f64) {
522    (f64::from(r.x0 + r.x1) / 2.0, f64::from(r.y0 + r.y1) / 2.0)
523}
524
525/// The floorplan of `units`, each in the order given at its candidate of least cost: the width of each of its wires to
526/// a unit placed before it or to a fixed site, times the straight distance between centres, so a unit sits where its
527/// wires meet; a candidate overlaps no unit placed before it nor `blocked`. Ties go to the smaller perimeter (the more
528/// compact region), then area, then the top row and left column. A unit no candidate holds is refused by name.
529pub fn place(geometry: &Geometry, units: &[Unit], wires: &[Wire], fill: Fill, aspect: f64, blocked: &[Rect]) -> Result<Vec<Assignment>, PlaceError> {
530    let mut plan: Vec<Assignment> = Vec::new();
531    for (k, unit) in units.iter().enumerate() {
532        let taken: Vec<Rect> = blocked.iter().copied().chain(plan.iter().map(|a| a.rect)).collect();
533        let cost = |r: &Rect| -> f64 {
534            let (cx, cy) = centre(r);
535            let to = |(x, y): (f64, f64)| ((cx - x).powi(2) + (cy - y).powi(2)).sqrt();
536            wires
537                .iter()
538                .map(|w| match *w {
539                    Wire::Units(a, b, width) if (a == k && b < k) || (b == k && a < k) => {
540                        let other = if a == k { b } else { a };
541                        width as f64 * to(centre(&plan[other].rect))
542                    }
543                    Wire::Site(u, (x, y), width) if u == k => width as f64 * to((f64::from(x), f64::from(y))),
544                    _ => 0.0,
545                })
546                .sum()
547        };
548        let best = candidates(geometry, unit.needs, fill, aspect, &taken)
549            .into_iter()
550            .map(|r| (cost(&r), (r.x1 - r.x0) + (r.y1 - r.y0), r.sites(), r))
551            .min_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)).then(a.2.cmp(&b.2)).then((a.3.y0, a.3.x0).cmp(&(b.3.y0, b.3.x0))))
552            .ok_or_else(|| PlaceError::NoRegion(unit.named()))?;
553        plan.push(Assignment { instances: unit.instances.clone(), name: unit.name.clone(), rect: best.3 });
554    }
555    Ok(plan)
556}
557
558/// The M20K blocks a memory of `depth` words of `width` bits takes: the fewest over the block's simple-dual-port shapes
559/// (512 × 40, 1,024 × 20, 2,048 × 10), as many blocks side by side as its width needs and stacked as its depth does, the
560/// stack a power of two deep (the fitter decodes it by the address's top bits).
561pub fn m20k_blocks(depth: u64, width: u64) -> u64 {
562    blocks(depth, width, None)
563}
564
565/// The M20K blocks of `m20k_blocks`, with byte enables of `byte` bits a block's every 10-bit lane holding one byte.
566fn blocks(depth: u64, width: u64, byte: Option<u64>) -> u64 {
567    let stack = depth.max(1).next_power_of_two();
568    [(512u64, 40u64), (1024, 20), (2048, 10)]
569        .iter()
570        .map(|&(d, w)| width.max(1).div_ceil(byte.map_or(w, |b| w / 10 * b.clamp(1, 10))) * stack.div_ceil(d))
571        .min()
572        .expect("three shapes")
573}
574
575/// A memory as synthesis left it: its path, the block it asked for (`AUTO`, `M20K block`, `MLAB`), each port's depth and
576/// width, its byte enables' byte when it has them, and whether a read on one port during a write on the other must give
577/// the old data.
578#[derive(Clone, Debug, PartialEq, Eq)]
579pub struct Ram {
580    pub name: String,
581    pub kind: String,
582    pub ports: Vec<(u64, u64)>,
583    pub byte: Option<u64>,
584    pub old_data: bool,
585}
586
587impl Ram {
588    /// The M20K blocks the fitter gives the memory: none for one asked into MLABs, none for an `AUTO` one the fitter
589    /// turns into MLABs, and otherwise its costlier port's blocks (`m20k_blocks`, byte-enabled lanes holding a byte
590    /// each). The fitter turns an `AUTO` memory into MLABs when it is small (every port 32 words of at most 42 bits, 64
591    /// of at most 10, or 128 of at most 5, as hybrid7b's fit did) and needs no old data across its ports, which an MLAB
592    /// cannot give.
593    pub fn m20k(&self) -> u64 {
594        let small = |&(d, w): &(u64, u64)| (d <= 32 && w <= 42) || (d <= 64 && w <= 10) || (d <= 128 && w <= 5);
595        let mlab = self.kind == "MLAB" || (self.kind == "AUTO" && !self.old_data && self.ports.iter().all(small));
596        if mlab {
597            return 0;
598        }
599        self.ports.iter().map(|&(d, w)| blocks(d, w, self.byte)).max().unwrap_or(0)
600    }
601}
602
603/// Every memory of the synthesis report's RAM summaries (one a partition), each with what its inferred parameter section
604/// (`Parameter Settings for Inferred Entity Instance: <path>`, the path its name continues) says of its byte enables and
605/// of a read across its ports; a memory no section names has neither.
606pub fn synthesized_rams(syn: &str) -> Vec<Ram> {
607    let mut sections: std::collections::HashMap<&str, Vec<(&str, &str)>> = std::collections::HashMap::new();
608    let mut lines = syn.lines().peekable();
609    while let Some(line) = lines.next() {
610        let Some(path) = cells_of(line).filter(|c| c.len() == 1).and_then(|c| c[0].strip_prefix("Parameter Settings for Inferred Entity Instance: ")) else {
611            continue;
612        };
613        let mut rows = Vec::new();
614        while let Some(next) = lines.peek() {
615            if next.starts_with('+') {
616                lines.next();
617                continue;
618            }
619            let Some(c) = cells_of(next) else { break };
620            if let [name, value, ..] = c[..] {
621                rows.push((name, value));
622            }
623            lines.next();
624        }
625        sections.insert(path, rows);
626    }
627    let mut titles: Vec<&str> = syn
628        .lines()
629        .filter_map(cells_of)
630        .filter(|c| c.len() == 1 && c[0].starts_with("Synthesis RAM Summary for Partition "))
631        .map(|c| c[0])
632        .collect();
633    titles.dedup();
634    let mut rams = Vec::new();
635    for title in titles {
636        let Some((header, rows)) = headed_table(syn, title) else { continue };
637        let col = |name: &str| header.iter().position(|h| *h == name);
638        let (Some(name), Some(kind), Some(da), Some(wa), Some(db), Some(wb)) =
639            (col("Name"), col("Type"), col("Port A Depth"), col("Port A Width"), col("Port B Depth"), col("Port B Width"))
640        else {
641            continue;
642        };
643        rams.extend(rows.iter().filter_map(|r| {
644            let path = *r.get(name)?;
645            let port = |d: usize, w: usize| Some((number(r.get(d)?)?, number(r.get(w)?)?));
646            let ports: Vec<(u64, u64)> = port(da, wa).into_iter().chain(port(db, wb)).collect();
647            let params = path.match_indices('|').rev().find_map(|(at, _)| sections.get(&path[..at]));
648            let value = |key: &str| params.and_then(|p| p.iter().find(|(k, _)| *k == key)).map(|(_, v)| *v);
649            let enabled = ["WIDTH_BYTEENA_A", "WIDTH_BYTEENA_B"].iter().any(|k| value(k).and_then(number).is_some_and(|n| n > 1));
650            let byte = enabled.then(|| value("BYTE_SIZE").and_then(number).unwrap_or(8));
651            let old_data = value("READ_DURING_WRITE_MODE_MIXED_PORTS") == Some("OLD_DATA");
652            Some(Ram { name: path.to_string(), kind: r.get(kind)?.to_string(), ports, byte, old_data })
653        }));
654    }
655    rams
656}
657
658/// What the instances `members` need placed together as Quartus synthesized them, from the synthesis report `syn`: the
659/// entity table's ALUTs, registers and DSP blocks of each member's whole subtree summed, those ALUTs and registers in
660/// ALMs at `packing` packed as one (the fitter packs one member's registers beside another's LUTs), and the M20K blocks
661/// the fitter gives every memory under any of them (`Ram::m20k`). The member the entity table does not name, when one
662/// is missing.
663pub fn synthesized_needs<S: AsRef<str>>(syn: &str, members: &[S], packing: &super::preflight::Packing) -> Result<Needs, String> {
664    let first = |cell: &str| number(cell.split('(').next()?);
665    let table = headed_table(syn, "Partition \"root_partition\" Resource Utilization by Entity");
666    let counts = |instance: &str| {
667        let (header, rows) = table.as_ref()?;
668        let col = |name: &str| header.iter().position(|h| *h == name);
669        let (aluts, registers, dsp, path) = (col("Combinational ALUTs")?, col("Dedicated Logic Registers")?, col("DSP Blocks")?, col("Full Hierarchy Name")?);
670        let row = rows.iter().find(|r| r.get(path) == Some(&instance))?;
671        Some((first(row.get(aluts)?)?, first(row.get(registers)?)?, first(row.get(dsp)?)?))
672    };
673    let rams = synthesized_rams(syn);
674    let (mut aluts, mut registers, mut dsp, mut m20k) = (0, 0, 0, 0);
675    for member in members.iter().map(AsRef::as_ref) {
676        let (a, r, d) = counts(member).ok_or_else(|| member.to_string())?;
677        let under = format!("{member}|");
678        (aluts, registers, dsp) = (aluts + a, registers + r, dsp + d);
679        m20k += rams.iter().filter(|ram| ram.name.starts_with(&under)).map(Ram::m20k).sum::<u64>();
680    }
681    Ok(Needs { alms: packing.alms(aluts as f64, registers as f64), registers: registers as f64, m20k, dsp })
682}
683
684/// Where an instance's clock comes from: the instance, the clock its paths run on, and the site that clock leaves (a
685/// memory channel's, its bank's IO tile control).
686#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
687pub struct Anchor {
688    pub instance: String,
689    pub clock: String,
690    pub site: (u32, u32),
691}
692
693/// Why a build's reports place no anchor for an instance.
694#[derive(Clone, Debug, PartialEq, Eq)]
695pub enum AnchorError {
696    /// No one clock the instance's setup paths run on.
697    NoClock(String),
698    /// The instance's clock is not in the clock table.
699    NoTargets(String),
700    /// No one fast signal the clock comes from.
701    NoSite(String),
702}
703
704impl std::fmt::Display for AnchorError {
705    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
706        match self {
707            AnchorError::NoClock(instance) => write!(f, "{instance}: no one clock its setup paths run on"),
708            AnchorError::NoTargets(clock) => write!(f, "{clock}: not in the clock table"),
709            AnchorError::NoSite(clock) => write!(f, "{clock}: no one fast signal it comes from"),
710        }
711    }
712}
713
714/// The anchors ledger (`benchmarks/silicon/d5005/emif_anchors.json`): what it holds, the build whose reports placed the
715/// anchors, the day they were recorded, and the anchors.
716#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
717pub struct Anchors {
718    pub what: String,
719    pub build: String,
720    pub recorded: String,
721    pub anchors: Vec<Anchor>,
722}
723
724/// The anchor of `instance` from a fitted build's reports: the clock its setup paths run on (`instance_clock`), that
725/// clock's targets in the clock table, and the fast signal of the place report those targets come from (`clock_site`).
726pub fn anchor(place: &str, clocks: &str, setup: &str, instance: &str) -> Result<Anchor, AnchorError> {
727    let clock = instance_clock(setup, instance).ok_or_else(|| AnchorError::NoClock(instance.to_string()))?;
728    let table = clock_targets(clocks);
729    let (_, targets) = table.iter().find(|(c, _)| *c == clock).ok_or_else(|| AnchorError::NoTargets(clock.clone()))?;
730    let site = clock_site(targets, &fast_signal_sites(place)).ok_or_else(|| AnchorError::NoSite(clock.clone()))?;
731    Ok(Anchor { instance: instance.to_string(), clock, site })
732}
733
734/// A Logic Lock region's name for an instance path: `logos_` and the path, its levels and a generate block's scope parted
735/// by `_`, an index's brackets dropped (`ch[0].channel` is `logos_ch0_channel`).
736pub fn region_name(path: &str) -> String {
737    let mut name = String::from("logos_");
738    for c in path.chars() {
739        match c {
740            '[' | ']' => {}
741            c if c.is_ascii_alphanumeric() || c == '_' => name.push(c),
742            _ => name.push('_'),
743        }
744    }
745    name
746}
747
748/// The units a floorplan places and its wires, from a module's graph: each of `units` (its instances paths under the
749/// module `graph` was read from, its region's name, what its instances need together) placed under `under` (the
750/// module's own path in the build); a wire between two units of the bits the graph counts between an instance of the
751/// one and an instance of the other (bits between two instances of one unit stay inside its region); and from a unit to
752/// an anchor (matched by an instance's full path) of the bits that instance shares with the module's ports, the way out
753/// to the anchor. Refused by name: an instance the module lacks, a unit of none, an instance two units hold, and two
754/// units of one region name.
755pub fn unit_wires(graph: &Graph, under: &str, units: &[Unit], anchors: &[Anchor]) -> Result<(Vec<Unit>, Vec<Wire>), PlaceError> {
756    let full = |path: &str| if under.is_empty() { path.to_string() } else { format!("{under}|{path}") };
757    let mut placing: Vec<Unit> = Vec::new();
758    for (k, unit) in units.iter().enumerate() {
759        if unit.instances.is_empty() {
760            return Err(PlaceError::Empty(unit.name.clone()));
761        }
762        if let Some(path) = unit.instances.iter().find(|p| !graph.instances.contains(p)) {
763            return Err(PlaceError::Unknown(path.clone()));
764        }
765        if let Some(path) = unit.instances.iter().enumerate().find(|(i, p)| units[..k].iter().any(|u| u.instances.contains(p)) || unit.instances[..*i].contains(p)).map(|(_, p)| p) {
766            return Err(PlaceError::Twice(path.clone()));
767        }
768        if let Some(other) = units[..k].iter().find(|u| u.name == unit.name) {
769            return Err(PlaceError::SameName(other.named(), unit.named()));
770        }
771        placing.push(Unit { instances: unit.instances.iter().map(|p| full(p)).collect(), name: unit.name.clone(), needs: unit.needs });
772    }
773    let index = |n: &str| units.iter().position(|u| u.instances.iter().any(|p| p == n));
774    let mut wires: Vec<Wire> = graph
775        .links
776        .iter()
777        .filter_map(|l| Some((index(&l.a)?, index(&l.b)?, l.bits)))
778        .filter(|(a, b, _)| a != b)
779        .map(|(a, b, bits)| Wire::Units(a, b, bits))
780        .collect();
781    for (k, unit) in units.iter().enumerate() {
782        for path in &unit.instances {
783            let out = graph.ports.iter().find(|(n, _)| n == path).map_or(0, |(_, bits)| *bits);
784            wires.extend(anchors.iter().filter(|a| a.instance == full(path)).map(|a| Wire::Site(k, a.site, out)));
785        }
786    }
787    Ok((placing, wires))
788}
789
790/// The instances of `graph` no unit of `units` holds, each with the bits its links carry to the units' instances,
791/// heaviest first (then by path), those carrying none left out: a path between two units through an instance no unit
792/// holds is no wire of the floorplan's (`unit_wires` counts links between units' instances alone) until its module is
793/// dissolved into the graph (`netlist::module_graph`'s `through`).
794pub fn loose(graph: &Graph, units: &[Unit]) -> Vec<(String, u64)> {
795    let held = |n: &str| units.iter().any(|u| u.instances.iter().any(|p| p == n));
796    let mut carried: Vec<(String, u64)> = graph
797        .instances
798        .iter()
799        .filter(|i| !held(i))
800        .map(|i| {
801            let bits = graph.links.iter().filter(|l| (l.a == *i && held(&l.b)) || (l.b == *i && held(&l.a))).map(|l| l.bits).sum();
802            (i.clone(), bits)
803        })
804        .filter(|(_, bits)| *bits > 0)
805        .collect();
806    carried.sort_by(|(a, x), (b, y)| y.cmp(x).then_with(|| a.cmp(b)));
807    carried
808}
809
810/// The floorplan of `units` in rectangles (`place`) on the wires `unit_wires` reads off the module's graph.
811#[allow(clippy::too_many_arguments)]
812pub fn unit_floorplan(
813    geometry: &Geometry,
814    graph: &Graph,
815    under: &str,
816    units: &[Unit],
817    anchors: &[Anchor],
818    fill: Fill,
819    aspect: f64,
820    blocked: &[Rect],
821) -> Result<Vec<Assignment>, PlaceError> {
822    let (placing, wires) = unit_wires(graph, under, units, anchors)?;
823    place(geometry, &placing, &wires, fill, aspect, blocked)
824}
825
826/// The floorplan of `units` in regions of whole tiles (`grow`) on the wires `unit_wires` reads off the module's graph.
827pub fn unit_regions(
828    geometry: &Geometry,
829    graph: &Graph,
830    under: &str,
831    units: &[Unit],
832    anchors: &[Anchor],
833    fill: Fill,
834    blocked: &[Rect],
835) -> Result<Vec<Region>, PlaceError> {
836    let (placing, wires) = unit_wires(graph, under, units, anchors)?;
837    grow(geometry, &placing, &wires, fill, blocked)
838}
839
840/// A unit's region of whole tiles: the instance, its name, its tiles in the map's order (top row first, then left).
841#[derive(Clone, Debug, PartialEq, Eq)]
842pub struct Region {
843    pub instances: Vec<String>,
844    pub name: String,
845    pub tiles: Vec<Rect>,
846}
847
848impl Region {
849    /// The rectangles the region's tiles merge into: each row's runs of tiles side by side, then runs of one span in rows
850    /// one above the other, in the map's order.
851    pub fn rects(&self) -> Vec<Rect> {
852        let mut tiles = self.tiles.clone();
853        tiles.sort_by_key(|t| (t.y0, t.x0));
854        let mut runs: Vec<Rect> = Vec::new();
855        for t in tiles {
856            match runs.last_mut() {
857                Some(r) if r.y0 == t.y0 && r.y1 == t.y1 && r.x1 + 1 == t.x0 => r.x1 = t.x1,
858                _ => runs.push(t),
859            }
860        }
861        runs.sort_by_key(|r| (r.x0, r.x1, r.y0));
862        let mut merged: Vec<Rect> = Vec::new();
863        for r in runs {
864            match merged.last_mut() {
865                Some(m) if m.x0 == r.x0 && m.x1 == r.x1 && m.y1 + 1 == r.y0 => m.y1 = r.y1,
866                _ => merged.push(r),
867            }
868        }
869        merged.sort_by_key(|r| (r.y0, r.x0));
870        merged
871    }
872
873    /// The centre of the region's sites.
874    fn centre(&self) -> (f64, f64) {
875        let sites: f64 = self.tiles.iter().map(|t| t.sites() as f64).sum();
876        let (x, y) = self.tiles.iter().fold((0.0, 0.0), |(x, y), t| {
877            let (cx, cy) = centre(t);
878            (x + cx * t.sites() as f64, y + cy * t.sites() as f64)
879        });
880        (x / sites, y / sites)
881    }
882}
883
884/// The regions as Quartus settings: each a `PLACE_REGION` of its rectangles (`Region::rects`), its own name, core sites
885/// only, shared with the rest of the design.
886pub fn regions_tcl(regions: &[Region]) -> String {
887    let mut out = String::new();
888    for r in regions {
889        let rects = r.rects();
890        for instance in &r.instances {
891            settings(&mut out, instance, &r.name, &rects);
892        }
893    }
894    out
895}
896
897/// The floorplan of `units` in regions of whole tiles, each unit in the order given. Its seed is the free tile where its
898/// wires are shortest (each wire's width times the straight distance from the tile's centre to the centre of the unit
899/// at its other end, placed before it, or to its fixed site), among the free tiles of an area that holds it at `fill`,
900/// the top row and left column first on a tie. The region then takes, one at a time, the free tile beside it nearest
901/// the centre of its sites so far (then where the wires are shortest, then the top row and left column) until it holds
902/// the unit, so it grows as compact as the tiles allow and bends round what is taken. A tile is free when no region
903/// before it took it and it overlaps nothing `blocked`. A unit no free area holds is refused by name.
904pub fn grow(geometry: &Geometry, units: &[Unit], wires: &[Wire], fill: Fill, blocked: &[Rect]) -> Result<Vec<Region>, PlaceError> {
905    let tiles: Vec<Rect> = geometry.tiles.iter().map(|(r, _)| *r).collect();
906    let n = tiles.len();
907    let beside = |a: &Rect, b: &Rect| {
908        ((a.x1 + 1 == b.x0 || b.x1 + 1 == a.x0) && a.y0 <= b.y1 && b.y0 <= a.y1)
909            || ((a.y1 + 1 == b.y0 || b.y1 + 1 == a.y0) && a.x0 <= b.x1 && b.x0 <= a.x1)
910    };
911    let neighbours: Vec<Vec<usize>> = (0..n).map(|a| (0..n).filter(|&b| beside(&tiles[a], &tiles[b])).collect()).collect();
912    let mut taken: Vec<bool> = tiles.iter().map(|t| blocked.iter().any(|b| overlap(t, b))).collect();
913    let add = |c: Capacity, t: Capacity| Capacity { alms: c.alms + t.alms, registers: c.registers + t.registers, m20k: c.m20k + t.m20k, dsp: c.dsp + t.dsp };
914    let at = |t: usize| (tiles[t].y0, tiles[t].x0);
915    let mut plan: Vec<Region> = Vec::new();
916    for (k, unit) in units.iter().enumerate() {
917        let cost = |t: usize| -> f64 {
918            let (cx, cy) = centre(&tiles[t]);
919            let to = |(x, y): (f64, f64)| ((cx - x).powi(2) + (cy - y).powi(2)).sqrt();
920            wires
921                .iter()
922                .map(|w| match *w {
923                    Wire::Units(a, b, width) if (a == k && b < k) || (b == k && a < k) => {
924                        width as f64 * to(plan[if a == k { b } else { a }].centre())
925                    }
926                    Wire::Site(u, (x, y), width) if u == k => width as f64 * to((f64::from(x), f64::from(y))),
927                    _ => 0.0,
928                })
929                .sum()
930        };
931        let costs: Vec<f64> = (0..n).map(cost).collect();
932        // The free areas: each free tile's area, and whether the area holds the unit.
933        let mut area = vec![usize::MAX; n];
934        let mut holding: Vec<bool> = Vec::new();
935        for t in 0..n {
936            if taken[t] || area[t] != usize::MAX {
937                continue;
938            }
939            let (id, mut stack, mut have) = (holding.len(), vec![t], Capacity::default());
940            area[t] = id;
941            while let Some(a) = stack.pop() {
942                have = add(have, geometry.tiles[a].1);
943                for &b in &neighbours[a] {
944                    if !taken[b] && area[b] == usize::MAX {
945                        area[b] = id;
946                        stack.push(b);
947                    }
948                }
949            }
950            holding.push(holds(have, unit.needs, fill));
951        }
952        let seed = (0..n)
953            .filter(|&t| !taken[t] && holding[area[t]])
954            .min_by(|&a, &b| costs[a].total_cmp(&costs[b]).then(at(a).cmp(&at(b))))
955            .ok_or_else(|| PlaceError::NoRegion(unit.named()))?;
956        let mut inside = vec![false; n];
957        inside[seed] = true;
958        let (mut region, mut have) = (vec![seed], geometry.tiles[seed].1);
959        let weigh = |t: usize| {
960            let ((x, y), w) = (centre(&tiles[t]), tiles[t].sites() as f64);
961            (x * w, y * w, w)
962        };
963        let mut mass = weigh(seed);
964        while !holds(have, unit.needs, fill) {
965            let at_centre = (mass.0 / mass.2, mass.1 / mass.2);
966            let gap = |t: usize| {
967                let (x, y) = centre(&tiles[t]);
968                ((x - at_centre.0).powi(2) + (y - at_centre.1).powi(2)).sqrt()
969            };
970            let next = region
971                .iter()
972                .flat_map(|&t| neighbours[t].iter().copied())
973                .filter(|&b| !taken[b] && !inside[b])
974                .min_by(|&a, &b| gap(a).total_cmp(&gap(b)).then(costs[a].total_cmp(&costs[b])).then(at(a).cmp(&at(b))))
975                .expect("the seed's area holds the unit");
976            inside[next] = true;
977            region.push(next);
978            have = add(have, geometry.tiles[next].1);
979            let w = weigh(next);
980            mass = (mass.0 + w.0, mass.1 + w.1, mass.2 + w.2);
981        }
982        region.iter().for_each(|&t| taken[t] = true);
983        region.sort_by_key(|&t| at(t));
984        plan.push(Region { instances: unit.instances.clone(), name: unit.name.clone(), tiles: region.into_iter().map(|t| tiles[t]).collect() });
985    }
986    Ok(plan)
987}
988
989#[cfg(test)]
990#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/floorplan/tests.rs"]
991mod tests;