1use 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#[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 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
45pub 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
54pub 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
63pub 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
77fn shared_levels(a: &str, b: &str) -> usize {
79 a.split('|').zip(b.split('|')).take_while(|(x, y)| x == y).count()
80}
81
82pub 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
92pub 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#[derive(Clone, Copy, Debug, PartialEq)]
123pub struct Need {
124 pub alms: f64,
125 pub m20k: u64,
126}
127
128pub 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#[derive(Clone, Copy, Debug, PartialEq)]
141pub struct Density {
142 pub alms: f64,
143 pub m20k: f64,
144}
145
146impl Density {
147 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
155pub 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
164pub 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
179pub 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 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#[derive(Clone, Debug, PartialEq, Eq)]
229pub struct Assignment {
230 pub instances: Vec<String>,
231 pub name: String,
232 pub rect: Rect,
233}
234
235pub 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
247fn 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#[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
266pub 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
278pub 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
292pub 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#[derive(Clone, Debug, PartialEq)]
312pub struct Geometry {
313 pub tiles: Vec<(Rect, Capacity)>,
314}
315
316impl Geometry {
317 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 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#[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#[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 fn named(&self) -> String {
370 self.instances.first().cloned().unwrap_or_else(|| self.name.clone())
371 }
372}
373
374#[derive(Clone, Copy, Debug, PartialEq)]
377pub enum Wire {
378 Units(usize, usize, u64),
379 Site(usize, (u32, u32), u64),
380}
381
382#[derive(Clone, Debug, PartialEq, Eq)]
384pub enum PlaceError {
385 NoRegion(String),
387 Unknown(String),
389 SameName(String, String),
391 Empty(String),
393 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#[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 pub fn uniform(share: f64) -> Fill {
421 Fill { logic: share, m20k: share, dsp: share }
422 }
423}
424
425pub 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
434struct 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 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
485pub 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
520fn centre(r: &Rect) -> (f64, f64) {
522 (f64::from(r.x0 + r.x1) / 2.0, f64::from(r.y0 + r.y1) / 2.0)
523}
524
525pub 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
558pub fn m20k_blocks(depth: u64, width: u64) -> u64 {
562 blocks(depth, width, None)
563}
564
565fn 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#[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 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
603pub 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
658pub 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#[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#[derive(Clone, Debug, PartialEq, Eq)]
695pub enum AnchorError {
696 NoClock(String),
698 NoTargets(String),
700 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#[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
724pub 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
734pub 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
748pub 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
790pub 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#[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
826pub 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#[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 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 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
884pub 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
897pub 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 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;