app
AI Flow: Routing
Public Made by Adomby adom
The routing stage of AI Flow: routes a placed KiCad board connection by connection and reports what it made, what it could not and why. A guide, never a gate.
main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444
//! The coarse view of the board: what a detailed search can't see from where it stands.
//!
//! Two jobs share one map. First, a backward search from the target over coarse cells on each
//! routing layer, with vias as edges (VTR's router lookahead, Navit's search from the destination),
//! resumed only as far as the detailed search asks (reverse resumable A*). That gives the detailed
//! A* the true way round obstacles, and a quick "no path" when the map is closed. Second, the global
//! stage further down, which uses the same boundaries as capacities.
//!
//! The map must stay optimistic: "closed" is only ever a proof. A boundary between two cells is cut
//! into short samples; a sample is closed only when one obstacle is closer to its middle than the
//! narrowest legal centreline allows, by more than half the sample's length, so no point of that
//! sample could carry a legal track. A fine-pitch lane therefore can't be closed by rounding: The designer's
//! rule that a router's "no path" at fine pitch is not evidence (Troubleshooting, "Routing practices")
//! is why the map has no slop. Copper of the query's own net never closes anything.
use crate::board::*;
use crate::geom::*;
use crate::index::*;
use crate::rules::Rules;
use crate::search::{Query, Terminal, World};
use std::cmp::Reverse;
use std::collections::BinaryHeap;
/// A sample closed by copper of more than one net, or by something with no net.
const MANY: u16 = u16::MAX;
/// Samples per boundary. Must fit the u16 masks.
const K: usize = 5;
const FULL: u16 = (1u16 << K) - 1;
/// A track's keep-out at the neck is about 0.6 mm across, or 0.85 mm along an axis at 45 degrees,
/// so at this size no boundary line can slip through a wall of tracks at any angle.
const CELL: f64 = 0.25 * NM;
const VIA_SUB: usize = 2;
/// How much dearer the estimate makes a step off the planned corridor. Swept 0.3 and 1.0: the same
/// completion on all three boards within one connection, fewer bends at 0.3 (piprobe_v13 66 to 72).
const CORRIDOR_BIAS: f32 = 0.3;
/// What the coarse estimate charges a soft search per crossing of committed copper (the router's
/// cheapest price for taking a route's place).
const SOFT_CROSSING: f64 = 2.0 * NM;
#[inline]
fn net16(n: NetId) -> u16 {
if n == NO_NET { MANY } else { n.min(MANY as u32 - 1) as u16 }
}
pub struct Coarse {
pub x0: f64,
pub y0: f64,
pub cell: f64,
s: f64,
pub w: usize,
pub h: usize,
/// World layer of each coarse layer, and back.
pub route: Vec<usize>,
index: Vec<usize>,
/// Narrowest legal centreline's distance to copper, and to the board edge.
need: f64,
need_edge: f64,
/// Closed samples per boundary, as a bit mask: copper that stays, and committed routes.
mask_s: Vec<u16>,
mask_d: Vec<u16>,
/// Who closes each sample: 0 nobody, otherwise one net, or MANY.
blk_s: Vec<u16>,
blk_d: Vec<u16>,
/// Bumped when committed copper changes, and when anything else does.
pub rev_d: u64,
pub rev_s: u64,
}
impl Coarse {
pub fn build(w: &World, r: &Rules) -> Coarse {
let mut bb = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
let mut grow = |b: (f64, f64, f64, f64)| {
bb = (bb.0.min(b.0), bb.1.min(b.1), bb.2.max(b.2), bb.3.max(b.3));
};
for o in w.edge.iter() {
grow(o.bb);
}
for g in &w.layers {
for o in g.iter() {
if matches!(o.owner, Owner::Pad(_)) {
grow(o.bb);
}
}
}
let cell = CELL;
let s = cell / K as f64;
let x0 = bb.0 - 2.0 * cell;
let y0 = bb.1 - 2.0 * cell;
let cw = ((bb.2 - bb.0) / cell).ceil() as usize + 4;
let ch = ((bb.3 - bb.1) / cell).ceil() as usize + 4;
let route = w.route_layers.clone();
let mut index = vec![usize::MAX; w.layers.len()];
for (i, &l) in route.iter().enumerate() {
index[l] = i;
}
let n = route.len() * 2 * cw * ch;
let mut c = Coarse {
x0,
y0,
cell,
s,
w: cw,
h: ch,
route,
index,
need: r.neck as f64 / 2.0 + r.clearance as f64,
need_edge: r.neck as f64 / 2.0 + r.edge as f64,
mask_s: vec![0; n],
mask_d: vec![0; n],
blk_s: vec![0; n * K],
blk_d: vec![0; n * K],
rev_d: 1,
rev_s: 1,
};
for l in 0..c.route.len() {
let g = &w.layers[c.route[l]];
g.drain_log();
for o in g.iter() {
// "Closed" is a proof, and a pour some nets may cross proves nothing about them.
if let Owner::Zone(z) = o.owner {
if w.pour_open.get(z as usize).map(|v| !v.is_empty()).unwrap_or(false) {
continue;
}
}
c.mark(l, &o.shape, o.net, o.owner, c.need);
}
for o in w.edge.iter() {
c.mark(l, &o.shape, NO_NET, Owner::Edge, c.need_edge);
}
}
c
}
/// Bring the map up to date with what was committed and ripped since last time.
pub fn sync(&mut self, w: &World) {
for l in 0..self.route.len() {
let log = w.layers[self.route[l]].drain_log();
for (added, o) in log {
if added {
self.mark(l, &o.shape, o.net, o.owner, self.need);
} else {
self.unmark(w, l, &o.shape);
}
if matches!(o.owner, Owner::Conn(_)) {
self.rev_d += 1;
} else {
self.rev_s += 1;
self.rev_d += 1;
}
}
}
}
#[inline]
fn boundary(&self, l: usize, d: usize, i: usize, j: usize) -> usize {
((l * 2 + d) * self.h + j) * self.w + i
}
/// Visit the samples any obstacle piece could close. `d` 0: the boundary from (i,j) to
/// (i+1,j); 1: from (i,j) to (i,j+1).
fn near_samples(&self, bb: (f64, f64, f64, f64), mut f: impl FnMut(usize, usize, usize, usize, V)) {
let (w, h, cell, s, x0, y0) = (self.w as i64, self.h as i64, self.cell, self.s, self.x0, self.y0);
let lo = |v: f64, o: f64, st: f64| ((v - o) / st).ceil() as i64;
let hi = |v: f64, o: f64, st: f64| ((v - o) / st).floor() as i64;
let k = K as i64;
// Vertical boundary lines x = x0 + (i+1) cell, samples along y.
for line in lo(bb.0, x0, cell)..=hi(bb.2, x0, cell) {
let i = line - 1;
if i < 0 || i >= w {
continue;
}
for n in (hi(bb.1, y0, s)).max(0)..=(hi(bb.3, y0, s)).min(h * k - 1) {
let m = V(x0 + line as f64 * cell, y0 + (n as f64 + 0.5) * s);
f(0, i as usize, (n / k) as usize, (n % k) as usize, m);
}
}
for line in lo(bb.1, y0, cell)..=hi(bb.3, y0, cell) {
let j = line - 1;
if j < 0 || j >= h {
continue;
}
for n in (hi(bb.0, x0, s)).max(0)..=(hi(bb.2, x0, s)).min(w * k - 1) {
let m = V(x0 + (n as f64 + 0.5) * s, y0 + line as f64 * cell);
f(1, (n / k) as usize, j as usize, (n % k) as usize, m);
}
}
}
/// Long diagonal tracks have huge boxes, so a capsule is walked in short pieces.
fn pieces(&self, shape: &Shape, reach: f64) -> Vec<(f64, f64, f64, f64)> {
if let Shape::Capsule { a, b, r } = shape {
let len = hypot(sub(*a, *b));
let n = (len / (2.0 * self.cell)).ceil().max(1.0) as usize;
let rr = r + reach;
return (0..n)
.map(|i| {
let (t0, t1) = (i as f64 / n as f64, (i + 1) as f64 / n as f64);
let p = V(a.0 + (b.0 - a.0) * t0, a.1 + (b.1 - a.1) * t0);
let q = V(a.0 + (b.0 - a.0) * t1, a.1 + (b.1 - a.1) * t1);
(p.0.min(q.0) - rr, p.1.min(q.1) - rr, p.0.max(q.0) + rr, p.1.max(q.1) + rr)
})
.collect();
}
let b = shape.bbox();
vec![(b.0 - reach, b.1 - reach, b.2 + reach, b.3 + reach)]
}
fn mark(&mut self, l: usize, shape: &Shape, net: NetId, owner: Owner, need: f64) {
let half = self.s / 2.0;
let who = net16(net);
let dynamic = matches!(owner, Owner::Conn(_));
let mut hits: Vec<(usize, usize)> = Vec::new();
if let Shape::Poly { pts, r } = shape {
// A pour can have thousands of points, and testing every sample in its box against all
// of them took 10 s on benchmark B. Its inside comes from scanlines, its rim from its
// edges as thin capsules.
self.poly_inside(l, pts, &mut hits);
let n = pts.len();
for i in 0..n {
let seg = Shape::Capsule { a: pts[i], b: pts[(i + 1) % n], r: *r };
for bb in self.pieces(&seg, need) {
self.near_samples(bb, |d, a, b, bit, m| {
if seg.dist_pt(m) + half < need {
hits.push((self.boundary(l, d, a, b), bit));
}
});
}
}
} else {
for bb in self.pieces(shape, need) {
self.near_samples(bb, |d, i, j, bit, m| {
if shape.dist_pt(m) + half < need {
hits.push((self.boundary(l, d, i, j), bit));
}
});
}
}
for (b, bit) in hits {
let (blk, mask) = if dynamic { (&mut self.blk_d, &mut self.mask_d) } else { (&mut self.blk_s, &mut self.mask_s) };
let v = &mut blk[b * K + bit];
*v = if *v == 0 || *v == who { who } else { MANY };
mask[b] |= 1 << bit;
}
}
/// Samples strictly inside a polygon, boundary line by boundary line.
fn poly_inside(&self, l: usize, pts: &[V], hits: &mut Vec<(usize, usize)>) {
let n = pts.len();
let k = K as i64;
let mut xs: Vec<f64> = Vec::new();
let mut bb = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
for p in pts {
bb = (bb.0.min(p.0), bb.1.min(p.1), bb.2.max(p.0), bb.3.max(p.1));
}
// d 0: lines of constant x, walking y; d 1: the other way round.
for d in 0..2usize {
let (lo, hi, o, t_o, t_n, l_n) = if d == 0 { (bb.0, bb.2, self.x0, self.y0, self.h as i64, self.w as i64) } else { (bb.1, bb.3, self.y0, self.x0, self.w as i64, self.h as i64) };
let first = ((lo - o) / self.cell).ceil() as i64;
let last = ((hi - o) / self.cell).floor() as i64;
for line in first..=last {
let idx = line - 1;
if idx < 0 || idx >= l_n {
continue;
}
let at = o + line as f64 * self.cell;
xs.clear();
for i in 0..n {
let (a, b) = (pts[i], pts[(i + 1) % n]);
let (ax, ay, bx, by) = if d == 0 { (a.0, a.1, b.0, b.1) } else { (a.1, a.0, b.1, b.0) };
if (ax > at) != (bx > at) {
xs.push(ay + (at - ax) / (bx - ax) * (by - ay));
}
}
xs.sort_by(f64::total_cmp);
for w in xs.chunks_exact(2) {
let from = (((w[0] - t_o) / self.s).ceil() as i64).max(0);
let to = (((w[1] - t_o) / self.s).floor() as i64 - 1).min(t_n * k - 1);
for m in from..=to {
let (a, b) = if d == 0 { (idx as usize, (m / k) as usize) } else { ((m / k) as usize, idx as usize) };
hits.push((self.boundary(l, d, a, b), (m % k) as usize));
}
}
}
}
}
/// Something went away: work out again who closes each sample it could have closed.
fn unmark(&mut self, w: &World, l: usize, shape: &Shape) {
let (half, need) = (self.s / 2.0, self.need);
let mut todo: Vec<(usize, usize, V)> = Vec::new();
for bb in self.pieces(shape, need) {
self.near_samples(bb, |d, i, j, bit, m| {
if shape.dist_pt(m) + half < need {
todo.push((self.boundary(l, d, i, j), bit, m));
}
});
}
let g = &w.layers[self.route[l]];
for (b, bit, m) in todo {
let (mut sv, mut dv) = (0u16, 0u16);
let box_ = (m.0 - need, m.1 - need, m.0 + need, m.1 + need);
g.visit(box_, |o| {
if o.shape.dist_pt(m) + half < need {
let who = net16(o.net);
let v = if matches!(o.owner, Owner::Conn(_)) { &mut dv } else { &mut sv };
*v = if *v == 0 || *v == who { who } else { MANY };
}
true
});
let e = self.need_edge;
w.edge.visit((m.0 - e, m.1 - e, m.0 + e, m.1 + e), |o| {
if o.shape.dist_pt(m) + half < e {
sv = MANY;
}
true
});
self.blk_s[b * K + bit] = sv;
self.blk_d[b * K + bit] = dv;
let set = |mask: &mut u16, on: bool| {
if on { *mask |= 1 << bit } else { *mask &= !(1 << bit) }
};
set(&mut self.mask_s[b], sv != 0);
set(&mut self.mask_d[b], dv != 0);
}
}
/// Can a centreline of this net cross this boundary at all?
#[inline]
pub fn open(&self, b: usize, who: u16, soft: bool) -> bool {
let m = if soft { self.mask_s[b] } else { self.mask_s[b] | self.mask_d[b] };
if m != FULL {
return true;
}
for i in 0..K {
let (s, d) = (self.blk_s[b * K + i], self.blk_d[b * K + i]);
if (s == 0 || s == who) && (soft || d == 0 || d == who) {
return true;
}
}
false
}
#[inline]
pub fn cell_of(&self, p: V) -> (i64, i64) {
(((p.0 - self.x0) / self.cell).floor() as i64, ((p.1 - self.y0) / self.cell).floor() as i64)
}
#[inline]
pub fn layer_index(&self, l: usize) -> usize {
self.index[l]
}
/// Can a via stand anywhere in this cell? Same logic as the samples: only a proof counts.
fn via_open(&self, w: &World, r: &Rules, net: NetId, soft: bool, i: usize, j: usize) -> bool {
let sub = self.cell / VIA_SUB as f64;
let hd = sub * 0.7072;
let need = r.via as f64 / 2.0 + r.clearance as f64;
let edge = r.via as f64 / 2.0 + r.edge as f64;
for a in 0..VIA_SUB {
'sq: for b in 0..VIA_SUB {
let c = V(self.x0 + (i as f64 + (a as f64 + 0.5) / VIA_SUB as f64) * self.cell, self.y0 + (j as f64 + (b as f64 + 0.5) / VIA_SUB as f64) * self.cell);
let bb = (c.0 - need, c.1 - need, c.0 + need, c.1 + need);
for g in &w.layers {
let mut closed = false;
g.visit(bb, |o| {
if (o.net == net && net != NO_NET) || (soft && matches!(o.owner, Owner::Conn(_))) {
return true;
}
if o.shape.dist_pt(c) + hd < need {
closed = true;
return false;
}
true
});
if closed {
continue 'sq;
}
}
let mut closed = false;
w.edge.visit((c.0 - edge, c.1 - edge, c.0 + edge, c.1 + edge), |o| {
if o.shape.dist_pt(c) + hd < edge {
closed = true;
return false;
}
true
});
if closed {
continue 'sq;
}
return true;
}
}
false
}
}
/// One backward search, resumed as the detailed search asks. Keyed on everything that changes its
/// answer, so a later attempt for the same target with the same window and via rule reuses it.
pub struct Back {
key: (NetId, u64, bool, bool, (i64, i64, i64, i64), u64),
corridor: Option<Rc<Corridor>>,
rev: u64,
i0: i64,
j0: i64,
cw: usize,
ch: usize,
nl: usize,
g: Vec<f32>,
closed: Vec<bool>,
via: Vec<u8>,
heap: BinaryHeap<Reverse<(u32, u32)>>,
src: (i64, i64),
net: NetId,
soft: bool,
no_vias: bool,
pub pops: u64,
}
/// Terminals by where they are, whichever layers a search allows them on: a bus member's first
/// tries are held to its layer, and it should still find its corridor.
fn ends_hash(ts: &[Terminal]) -> u64 {
let mut h = 0xcbf2_9ce4_8422_2325u64;
for t in ts {
if let Terminal::Point { p, .. } = t {
h = (h ^ p.0 as u64).wrapping_mul(0x0000_0100_0000_01b3);
h = (h ^ p.1 as u64).wrapping_mul(0x0000_0100_0000_01b3);
}
}
h
}
fn terminal_hash(ts: &[Terminal]) -> u64 {
let mut h = 0xcbf2_9ce4_8422_2325u64;
let mut mix = |v: u64| h = (h ^ v).wrapping_mul(0x0000_0100_0000_01b3);
for t in ts {
match t {
Terminal::Point { p, layers } => {
mix(p.0 as u64);
mix(p.1 as u64);
for &l in layers {
mix(l as u64 + 1);
}
}
Terminal::Zone { layer, poly } => {
mix(*layer as u64 + 100);
mix(poly.len() as u64);
if let Some(p) = poly.first() {
mix(p.0.to_bits());
mix(p.1.to_bits());
}
}
Terminal::Track { a, b, layer, .. } => {
mix(*layer as u64 + 200);
mix(a.0 as u64);
mix(a.1 as u64);
mix(b.0 as u64);
mix(b.1 as u64);
}
}
}
h
}
impl Back {
fn state(&self, i: i64, j: i64, l: usize) -> Option<usize> {
let (a, b) = (i - self.i0, j - self.j0);
if a < 0 || b < 0 || a >= self.cw as i64 || b >= self.ch as i64 {
return None;
}
Some((l * self.ch + b as usize) * self.cw + a as usize)
}
#[inline]
fn est(&self, i: i64, j: i64, cell: f64) -> f32 {
let (dx, dy) = ((i - self.src.0).abs() as f64, (j - self.src.1).abs() as f64);
let (hi, lo) = if dx > dy { (dx, dy) } else { (dy, dx) };
(((hi - lo) + lo * SQRT2) * cell) as f32
}
fn relax(&mut self, cell: f64, s: usize, i: i64, j: i64, g: f32) {
if g < self.g[s] {
self.g[s] = g;
let f = g + self.est(i, j, cell);
// Micrometres: enough to order cells of half a millimetre.
self.heap.push(Reverse(((f / 1000.0) as u32, s as u32)));
}
}
/// Settle states until this one is settled, or nothing is left.
fn lookup(&mut self, c: &Coarse, w: &World, r: &Rules, i: i64, j: i64, l: usize) -> f32 {
let Some(want) = self.state(i, j, l) else { return f32::INFINITY };
if self.closed[want] {
return self.g[want];
}
let who = net16(self.net);
let soft = self.soft;
while let Some(Reverse((_, s))) = self.heap.pop() {
let s = s as usize;
if self.closed[s] {
continue;
}
self.closed[s] = true;
self.pops += 1;
let (ci, cj, cl) = (s % self.cw, (s / self.cw) % self.ch, s / (self.cw * self.ch));
let (ai, aj) = (ci as i64 + self.i0, cj as i64 + self.j0);
let g = self.g[s];
let mut unit = c.cell as f32 * if c.route[cl] == 0 { 1.0 } else { r.cost_bottom as f32 };
// Off the planned corridor costs more in the estimate only: a preference, not a wall.
if let Some(cr) = &self.corridor {
if !cr.contains(ai, aj, cl) {
unit *= 1.0 + CORRIDOR_BIAS;
}
}
// What crossing a boundary adds, or infinity when it is closed. A soft search may cross
// committed routes at a price, and an estimate that knows this heads straight for the
// crossing instead of flooding everything cheaper first.
let cross = |d: usize, a: i64, b: i64| -> f32 {
if a < 0 || b < 0 || a as usize >= c.w || b as usize >= c.h {
return f32::INFINITY;
}
let e = c.boundary(cl, d, a as usize, b as usize);
if c.open(e, who, false) {
0.0
} else if soft && c.open(e, who, true) {
SOFT_CROSSING as f32
} else {
f32::INFINITY
}
};
let e = cross(0, ai, aj);
let wst = cross(0, ai - 1, aj);
let so = cross(1, ai, aj);
let n = cross(1, ai, aj - 1);
for (x, dx, dy) in [(e, 1i64, 0i64), (wst, -1, 0), (so, 0, 1), (n, 0, -1)] {
if x.is_finite() {
if let Some(t) = self.state(ai + dx, aj + dy, cl) {
self.relax(c.cell, t, ai + dx, aj + dy, g + unit + x);
}
}
}
// A diagonal step needs one of its two L-shaped ways open: any 45-degree line passes
// through one of them.
for (dx, dy, a, b) in [(1i64, 1i64, e, so), (1, -1, e, n), (-1, 1, wst, so), (-1, -1, wst, n)] {
let via_x = a + if dy > 0 { cross(1, ai + dx, aj) } else { cross(1, ai + dx, aj - 1) };
let via_y = b + if dx > 0 { cross(0, ai, aj + dy) } else { cross(0, ai - 1, aj + dy) };
let x = via_x.min(via_y);
if x.is_finite() {
if let Some(t) = self.state(ai + dx, aj + dy, cl) {
self.relax(c.cell, t, ai + dx, aj + dy, g + unit * SQRT2 as f32 + x);
}
}
}
if !self.no_vias && self.nl > 1 {
let v = ci + cj * self.cw;
if self.via[v] == 0 {
self.via[v] = if c.via_open(w, r, self.net, soft, ai as usize, aj as usize) { 1 } else { 2 };
}
if self.via[v] == 1 {
for o in 0..self.nl {
if o != cl {
let t = (o * self.ch + cj) * self.cw + ci;
self.relax(c.cell, t, ai, aj, g + r.cost_via as f32);
}
}
}
}
if s == want {
return g;
}
}
f32::INFINITY
}
}
/// What the search keeps between calls: the map and its recent backward searches.
#[derive(Default)]
pub struct Guide {
pub coarse: Option<Coarse>,
tables: Vec<Back>,
pub back_pops: u64,
pub closed_early: u64,
pub pockets_closed: u64,
pub searches: u64,
pub nodes_ok: u64,
pub nodes_failed: u64,
/// The last query that failed, so a retry can afford a closer look.
failed: Option<(NetId, u64, u64)>,
pockets: Vec<((u64, NetId, bool, bool), u64, bool)>,
/// When the first search of the run began, for the hard stop inside a long search (4.6).
pub start: Option<crate::clock::Instant>,
pub plan: Option<GlobalPlan>,
/// What the connection being routed should look like, if it belongs to a bus.
pub hint: Option<Hint>,
}
/// A bus member's preference: its neighbour's shape carried over to its own pins, and a layer. Only
/// ever a small cost: the search still finds its own way where the neighbour's shape doesn't fit.
#[derive(Clone, Default)]
pub struct Hint {
pub net: NetId,
/// Runs to follow, and the layer each is on.
pub lanes: Vec<(V, V, usize)>,
pub layer: Option<usize>,
}
/// How much dearer a step away from the lanes is. All octilinear routes between two points of one
/// length tie, so this only has to break ties, and more than the search's own 20% inflation would
/// flood. At 0, 0.1 and 0.25 the boards' results vary with no trend (benchmark C 269, 287, 259 of 300);
/// 0.1 is what benchmark B's picture was judged at.
const OFF_LANE: f64 = 0.1;
impl Hint {
#[inline]
pub fn extra(&self, a: V, b: V, layer: usize) -> f64 {
let mut k = 0.0;
if let Some(l) = self.layer {
if l != layer {
k += OFF_LANE;
}
}
if !self.lanes.is_empty() && !self.lanes.iter().any(|(p, q, l)| *l == layer && pt_seg(a, *p, *q) <= 2000.0 && pt_seg(b, *p, *q) <= 2000.0) {
k += OFF_LANE;
}
k
}
}
/// The pocket check looks this far around a terminal, in squares of this size.
const POCKET_R: f64 = 2.0 * NM;
const POCKET_S: f64 = 0.05 * NM;
/// Is this terminal walled in? A flood at the fine scale from the pad, over squares that a single
/// obstacle doesn't provably cover, looking for the edge of the area, the other terminal, or a place
/// a via could stand. Measured on benchmark C: most failing searches come within two cells of a pin
/// they can't get into and then spend their whole node budget circling it. Same rule as the map:
/// walled in means proven, never rounded, and a lane that exactly fits stays open.
fn walled_in(c: &Coarse, w: &World, q: &Query, p: P, layers: &[usize], other: &[Terminal]) -> bool {
let n = (2.0 * POCKET_R / POCKET_S) as i64;
let (ox, oy) = (p.0 as f64 - POCKET_R, p.1 as f64 - POCKET_R);
let hd = POCKET_S * 0.7072;
let nl = c.route.len();
let soft = q.soft.is_some();
let un = n as usize;
let mut closed = vec![false; un * un * nl];
let centre = |i: i64, j: i64| V(ox + (i as f64 + 0.5) * POCKET_S, oy + (j as f64 + 0.5) * POCKET_S);
let region = (ox, oy, ox + 2.0 * POCKET_R, oy + 2.0 * POCKET_R);
let mut paint = |l: usize, shape: &Shape, bb: (f64, f64, f64, f64), need: f64| {
let lo = |v: f64, o: f64| (((v - need - o) / POCKET_S).floor() as i64).clamp(0, n);
let hi = |v: f64, o: f64| (((v + need - o) / POCKET_S).ceil() as i64).clamp(0, n);
for j in lo(bb.1, oy)..hi(bb.3, oy) {
for i in lo(bb.0, ox)..hi(bb.2, ox) {
let at = (l * un + j as usize) * un + i as usize;
if !closed[at] && shape.dist_pt(centre(i, j)) + hd < need {
closed[at] = true;
}
}
}
};
for l in 0..nl {
let need = c.need;
let r = (region.0 - need, region.1 - need, region.2 + need, region.3 + need);
w.layers[c.route[l]].visit(r, |o| {
if (o.net == q.net && q.net != NO_NET) || (soft && matches!(o.owner, Owner::Conn(_))) || w.crosses_pour(o, q.net) {
return true;
}
paint(l, &o.shape, o.bb, need);
true
});
let e = c.need_edge;
w.edge.visit((region.0 - e, region.1 - e, region.2 + e, region.3 + e), |o| {
paint(l, &o.shape, o.bb, e);
true
});
}
let goal: Vec<(i64, i64, usize)> = other
.iter()
.filter_map(|t| if let Terminal::Point { p: g, layers } = t { Some((*g, layers)) } else { None })
.flat_map(|(g, ls)| {
let (i, j) = (((g.0 as f64 - ox) / POCKET_S).floor() as i64, ((g.1 as f64 - oy) / POCKET_S).floor() as i64);
ls.iter().filter(|&&l| c.index[l] != usize::MAX).map(move |&l| (i, j, l)).collect::<Vec<_>>()
})
.map(|(i, j, l)| (i, j, c.index[l]))
.collect();
let mut seen = vec![false; un * un * nl];
let mut via_tried = vec![false; un * un / 4 + un + 1];
let mut stack: Vec<(i64, i64, usize)> = Vec::new();
let (si, sj) = (n / 2, n / 2);
for &l in layers {
if c.index[l] != usize::MAX {
let cl = c.index[l];
seen[(cl * un + sj as usize) * un + si as usize] = true;
stack.push((si, sj, cl));
}
}
let via_need = q.rules.via as f64 / 2.0 + q.rules.clearance as f64;
let via_edge = q.rules.via as f64 / 2.0 + q.rules.edge as f64;
let via_hd = 2.0 * POCKET_S * 0.7072;
let can_via = q.profile.max_vias > 0 && nl > 1;
while let Some((i, j, l)) = stack.pop() {
if i == 0 || j == 0 || i == n - 1 || j == n - 1 || goal.contains(&(i, j, l)) {
return false;
}
if can_via {
// Tested on squares twice the size, so each proof covers four flood squares.
let (vi, vj) = (i / 2, j / 2);
let tag = (vj * n / 2 + vi) as usize;
if !via_tried[tag] {
via_tried[tag] = true;
let m = V(ox + (vi as f64 + 0.5) * 2.0 * POCKET_S, oy + (vj as f64 + 0.5) * 2.0 * POCKET_S);
let bb = (m.0 - via_need, m.1 - via_need, m.0 + via_need, m.1 + via_need);
let mut stands = true;
for g in &w.layers {
g.visit(bb, |o| {
if (o.net == q.net && q.net != NO_NET) || (soft && matches!(o.owner, Owner::Conn(_))) {
return true;
}
if o.shape.dist_pt(m) + via_hd < via_need {
stands = false;
return false;
}
true
});
if !stands {
break;
}
}
if stands {
w.edge.visit((m.0 - via_edge, m.1 - via_edge, m.0 + via_edge, m.1 + via_edge), |o| {
if o.shape.dist_pt(m) + via_hd < via_edge {
stands = false;
return false;
}
true
});
}
if stands {
for o in 0..nl {
let at = (o * un + j as usize) * un + i as usize;
if !seen[at] && !closed[at] {
seen[at] = true;
stack.push((i, j, o));
}
}
}
}
}
for dj in -1..=1i64 {
for di in -1..=1i64 {
let (a, b) = (i + di, j + dj);
let at = (l * un + b as usize) * un + a as usize;
if !seen[at] && !closed[at] {
seen[at] = true;
stack.push((a, b, l));
}
}
}
}
true
}
impl Guide {
/// Prepare the backward search for a query, or say the query isn't guided. Pair centrelines
/// and fan-ins skip checks the map can't know about, so they stay out of it.
pub fn prepare(&mut self, w: &World, q: &Query, window: (f64, f64, f64, f64)) -> Option<usize> {
if q.also.is_some() || !q.free.is_empty() || q.targets.is_empty() || q.profile.neck < q.rules.neck || q.profile.clearance < q.rules.clearance {
return None;
}
if self.coarse.is_none() {
self.coarse = Some(Coarse::build(w, q.rules));
}
let c = self.coarse.as_mut().unwrap();
c.sync(w);
let c = self.coarse.as_ref().unwrap();
let soft = q.soft.is_some();
let no_vias = q.profile.max_vias == 0;
let (a, b) = (c.cell_of(V(window.0, window.1)), c.cell_of(V(window.2, window.3)));
let (i0, j0) = (a.0.max(0), a.1.max(0));
let (i1, j1) = (b.0.min(c.w as i64 - 1), b.1.min(c.h as i64 - 1));
if i0 > i1 || j0 > j1 {
return None;
}
let corridor = if soft || q.profile.margin >= 8.0 * NM { None } else { self.corridor_of(q) };
let key = (q.net, terminal_hash(q.targets), soft, no_vias, (i0, j0, i1, j1), terminal_hash(q.sources) * corridor.is_some() as u64);
let c = self.coarse.as_ref().unwrap();
let rev = if soft { c.rev_s } else { c.rev_d };
self.tables.retain(|t| t.rev == rev);
if let Some(i) = self.tables.iter().position(|t| t.key == key) {
return Some(i);
}
if self.tables.len() >= 8 {
self.tables.remove(0);
}
let (cw, ch, nl) = ((i1 - i0 + 1) as usize, (j1 - j0 + 1) as usize, c.route.len());
let n = cw * ch * nl;
let mut src = (0, 0);
for t in q.sources {
if let Terminal::Point { p, .. } = t {
src = c.cell_of((*p).into());
break;
}
}
let mut t = Back { key, corridor, rev, i0, j0, cw, ch, nl, g: vec![f32::INFINITY; n], closed: vec![false; n], via: vec![0; cw * ch], heap: BinaryHeap::new(), src, net: q.net, soft, no_vias, pops: 0 };
for term in q.targets {
match term {
// A tap onto a rail: every cell along the track is a goal.
Terminal::Track { a, b, layer, .. } => {
if c.index[*layer] == usize::MAX {
continue;
}
let l = c.index[*layer];
let (va, vb): (V, V) = ((*a).into(), (*b).into());
let steps = (hypot(sub(va, vb)) / (c.cell * 0.5)).ceil().max(1.0) as usize;
for k in 0..=steps {
let f = k as f64 / steps as f64;
let (i, j) = c.cell_of(V(va.0 + (vb.0 - va.0) * f, va.1 + (vb.1 - va.1) * f));
if let Some(s) = t.state(i, j, l) {
t.relax(c.cell, s, i, j, 0.0);
}
}
}
Terminal::Point { p, layers } => {
let (i, j) = c.cell_of((*p).into());
for &l in layers {
if c.index[l] != usize::MAX {
if let Some(s) = t.state(i, j, c.index[l]) {
t.relax(c.cell, s, i, j, 0.0);
}
}
}
}
Terminal::Zone { layer, poly } => {
if c.index[*layer] == usize::MAX {
continue;
}
let l = c.index[*layer];
let b = Shape::Poly { pts: poly.clone(), r: 0.0 }.bbox();
let (u, v) = (c.cell_of(V(b.0, b.1)), c.cell_of(V(b.2, b.3)));
let reach = c.cell * 0.7072;
let edge = Shape::Poly { pts: poly.clone(), r: 0.0 };
for j in u.1.max(j0)..=v.1.min(j1) {
for i in u.0.max(i0)..=v.0.min(i1) {
let m = V(c.x0 + (i as f64 + 0.5) * c.cell, c.y0 + (j as f64 + 0.5) * c.cell);
// dist_pt is zero inside the outline.
if edge.dist_pt(m) <= reach {
if let Some(s) = t.state(i, j, l) {
t.relax(c.cell, s, i, j, 0.0);
}
}
}
}
}
}
}
self.tables.push(t);
Some(self.tables.len() - 1)
}
/// One line for the run's report: what the search spent, and what the map saved it.
pub fn summary(&self) -> String {
format!("search: {} searches, {} nodes in those that succeeded, {} in those that failed; {} closed by the coarse map, {} by a walled-in terminal; {} coarse states", self.searches, self.nodes_ok, self.nodes_failed, self.closed_early, self.pockets_closed, self.back_pops)
}
/// Could a via stand in this point's cell? False is a proof, as everywhere on the map.
pub fn via_possible(&mut self, table: usize, w: &World, r: &Rules, p: V) -> bool {
let c = self.coarse.as_ref().unwrap();
let (i, j) = c.cell_of(p);
let t = &mut self.tables[table];
let (a, b) = (i - t.i0, j - t.j0);
if a < 0 || b < 0 || a >= t.cw as i64 || b >= t.ch as i64 {
return true;
}
let v = b as usize * t.cw + a as usize;
if t.via[v] == 0 {
t.via[v] = if c.via_open(w, r, t.net, t.soft, i as usize, j as usize) { 1 } else { 2 };
}
t.via[v] == 1
}
pub fn corridor_of(&self, q: &Query) -> Option<Rc<Corridor>> {
let plan = self.plan.as_ref()?;
plan.corridors.get(&(q.net, ends_hash(q.sources), ends_hash(q.targets))).cloned()
}
pub fn note_failed(&mut self, q: &Query) {
if let Some(c) = &self.coarse {
self.failed = Some((q.net, terminal_hash(q.targets), if q.soft.is_some() { c.rev_s } else { c.rev_d }));
}
}
/// Retrying after a failure: is either end walled in? Only then is the closer look worth it,
/// and the answer carries over to every later attempt for the same pins.
pub fn hopeless(&mut self, w: &World, q: &Query) -> bool {
let Some(c) = self.coarse.as_ref() else { return false };
let soft = q.soft.is_some();
let rev = if soft { c.rev_s } else { c.rev_d };
if self.failed != Some((q.net, terminal_hash(q.targets), rev)) {
return false;
}
let no_vias = q.profile.max_vias == 0;
self.pockets.retain(|x| x.1 == rev);
let mut closed = false;
for (ours, others) in [(q.sources, q.targets), (q.targets, q.sources)] {
for t in ours {
let Terminal::Point { p, layers } = t else { continue };
if others.iter().any(|o| matches!(o, Terminal::Zone { .. })) {
continue;
}
let key = (terminal_hash(std::slice::from_ref(t)) ^ terminal_hash(others), q.net, soft, no_vias);
let v = match self.pockets.iter().find(|x| x.0 == key) {
Some(x) => x.2,
None => {
let v = walled_in(c, w, q, *p, layers, others);
self.pockets.push((key, rev, v));
v
}
};
closed |= v;
}
}
if closed {
self.pockets_closed += 1;
}
closed
}
/// The coarse cost from a point on a layer to the target; infinity is a proof of no way there.
#[inline]
pub fn cost(&mut self, table: usize, w: &World, r: &Rules, p: V, layer: usize) -> f64 {
let c = self.coarse.as_ref().unwrap();
let (i, j) = c.cell_of(p);
let t = &mut self.tables[table];
let before = t.pops;
let g = t.lookup(c, w, r, i, j, c.index[layer]);
self.back_pops += t.pops - before;
if g.is_finite() {
// Each end of a real route can be up to a cell closer than the cell centres say.
(g as f64 - 2.0 * c.cell).max(0.0)
} else {
f64::INFINITY
}
}
}
// ---------------------------------------------------------------------------------------------
// The global stage: before any copper is drawn, give every connection a corridor of coarse cells.
//
// Negotiation lives here and only here. PathFinder at lattice level scored poorly on ordinary boards
// (OrthoRoute's README cites 0.02 clean pass), while as an iterated assignment over a coarse network
// it converges (SUMO's duaIterate, VTR's rounds): route everything, price what is overused, repeat,
// with present and accumulated cost as in VTR's get_rr_cong_cost. A corridor can never make a route
// illegal: the detailed search still checks every step against real copper, and it only reads the
// corridor as a preference in its estimate, dropped when the search has to widen.
// ---------------------------------------------------------------------------------------------
use crate::board::Board;
use crate::classify::{Level, Task};
use std::collections::HashMap;
use std::rc::Rc;
const ROUNDS: usize = 16;
/// VTR's defaults, roughly: first present-cost factor, its growth per round, and the history step.
const PRES_INIT: f32 = 0.5;
const PRES_MULT: f32 = 1.5;
const ACC_FAC: f32 = 0.5;
/// A corridor: which global cells this connection plans to use, per layer.
pub struct Corridor {
pub bbox: (f64, f64, f64, f64),
on: Vec<bool>,
i0: i64,
j0: i64,
gw: usize,
gh: usize,
f: i64,
nl: usize,
}
impl Corridor {
#[inline]
fn contains(&self, ci: i64, cj: i64, l: usize) -> bool {
let (i, j) = (ci.div_euclid(self.f) - self.i0, cj.div_euclid(self.f) - self.j0);
i >= 0 && j >= 0 && (i as usize) < self.gw && (j as usize) < self.gh && l < self.nl && self.on[(l * self.gh + j as usize) * self.gw + i as usize]
}
}
/// What the global stage hands back to the router: a preference per connection and an order hint.
#[derive(Default, Clone)]
pub struct GlobalPlan {
corridors: HashMap<(NetId, u64, u64), Rc<Corridor>>,
pub note: String,
}
struct Conn {
a: (i64, i64, Vec<usize>),
b: (i64, i64, Vec<usize>),
demand: f32,
path: Vec<(i64, i64, usize)>,
key: (NetId, u64, u64),
}
fn env_on(name: &str) -> bool {
std::env::var_os(name).is_some()
}
fn pad_term(board: &Board, w: &World, i: usize) -> Terminal {
let p = &board.pads[i];
Terminal::Point { p: p.center, layers: p.layers.iter().copied().filter(|l| w.route_layers.contains(l)).collect() }
}
/// Assign every connection a corridor by negotiated congestion. Called once per routing pass, with
/// the pairs already placed so their room is gone.
///
/// The plan doesn't change the routing order. Tried at 60 s: most contested first lost connections
/// against shortest first (benchmark C 265 against 268), least contested first won on benchmark C (271)
/// and lost on piprobe_v13 (86 against 87). That is within the run-to-run spread, so the order stays
/// as the router sets it.
pub fn plan(w: &World, r: &Rules, board: &Board, tasks: &[Task], held_to: &dyn Fn(&Task) -> Option<usize>) -> GlobalPlan {
let t0 = std::time::Instant::now();
let mut out = GlobalPlan::default();
if env_on("ADOM_ROUTE_NO_GLOBAL") {
w.guide.borrow_mut().plan = None;
return out;
}
let mut guide = w.guide.borrow_mut();
if guide.coarse.is_none() {
guide.coarse = Some(Coarse::build(w, r));
}
let c = guide.coarse.as_mut().unwrap();
c.sync(w);
let c = guide.coarse.as_ref().unwrap();
// Global cells of 1 mm on a molecule, 2 mm on a large board, so a round stays in milliseconds.
let big = c.w as f64 * c.cell > 60.0 * NM || c.h as f64 * c.cell > 60.0 * NM;
let f: usize = if big { 8 } else { 4 };
let g = c.cell * f as f64;
let (gw, gh, nl) = (c.w / f, c.h / f, c.route.len());
if gw < 2 || gh < 2 {
return out;
}
let pitch = (r.track + r.clearance) as f64;
// Capacity of each global boundary: tracks that fit in each free run of real centreline room.
// Layer l, direction d, (i, j): from global cell (i, j) to the next in x (d 0) or y (d 1).
let ne = nl * 2 * gw * gh;
let mut cap = vec![0f32; ne];
let edge = |l: usize, d: usize, i: usize, j: usize| ((l * 2 + d) * gh + j) * gw + i;
for l in 0..nl {
for d in 0..2 {
for j in 0..gh {
for i in 0..gw {
let mut tracks = 0.0f32;
let mut run = 0usize;
let mut flush = |run: &mut usize| {
if *run > 0 {
tracks += (*run as f64 * c.s / pitch).floor() as f32 + 1.0;
*run = 0;
}
};
for t in 0..f {
let (bi, bj) = if d == 0 { ((i + 1) * f - 1, j * f + t) } else { (i * f + t, (j + 1) * f - 1) };
if bi >= c.w || bj >= c.h {
continue;
}
let b = c.boundary(l, d, bi, bj);
let m = c.mask_s[b] | c.mask_d[b];
for bit in 0..K {
if m & (1 << bit) == 0 {
run += 1;
} else {
flush(&mut run);
}
}
}
flush(&mut run);
cap[edge(l, d, i, j)] = tracks;
}
}
}
}
let mut conns: Vec<Conn> = Vec::new();
let gcell = |p: P| -> (i64, i64) {
let (i, j) = c.cell_of(p.into());
(i.div_euclid(f as i64), j.div_euclid(f as i64))
};
for t in tasks {
if t.zone.is_some() || t.a == t.b {
continue;
}
let (ta, tb) = (pad_term(board, w, t.a), pad_term(board, w, t.b));
let (Terminal::Point { p: pa, layers: la }, Terminal::Point { p: pb, layers: lb }) = (&ta, &tb) else { continue };
if la.is_empty() || lb.is_empty() {
continue;
}
let (ca, cb) = (gcell(*pa), gcell(*pb));
let demand = if t.level == Level::Power && t.main {
let main = r.wide.get(board.net_name(t.net)).and_then(|v| v.first().copied()).unwrap_or(r.power_floor);
(((main + r.clearance) as f64) / pitch).max(1.0) as f32
} else {
1.0
};
let key = (t.net, ends_hash(std::slice::from_ref(&ta)), ends_hash(std::slice::from_ref(&tb)));
// A bus member with a layer chosen plans on that layer.
let on = |ls: &Vec<usize>| -> Vec<usize> {
match held_to(t) {
Some(l) if ls.contains(&l) => vec![c.index[l]],
_ => ls.iter().map(|&l| c.index[l]).collect(),
}
};
conns.push(Conn { a: (ca.0, ca.1, on(la)), b: (cb.0, cb.1, on(lb)), demand, path: Vec::new(), key });
}
// Long ones first in the first round, so short ones see where the long ones want to be.
let mut order: Vec<usize> = (0..conns.len()).collect();
let span = |x: &Conn| (x.a.0 - x.b.0).abs() + (x.a.1 - x.b.1).abs();
order.sort_by_key(|&i| -span(&conns[i]));
let mut occ = vec![0f32; ne];
let mut hist = vec![1f32; ne];
let mut pres = PRES_INIT;
let ng = gw * gh * nl;
let mut best = vec![f32::INFINITY; ng];
let mut prev = vec![u32::MAX; ng];
let mut stamp = vec![0u32; ng];
let mut tick = 0u32;
let mut heap: BinaryHeap<Reverse<(u32, u32)>> = BinaryHeap::new();
let base = (g * 0.75) as f32;
let via_cost = r.cost_via as f32;
let (mut first_over, mut last_over, mut rounds) = (0.0f32, 0.0f32, 0usize);
for round in 0..ROUNDS {
rounds = round + 1;
let mut changed = 0;
for &ci in &order {
let conn = &mut conns[ci];
// Only connections through something overused need a new corridor (incremental
// PathFinder); the rest keep theirs, as SUMO keeps most drivers on their routes.
let (mut over, mut has) = (false, false);
for s in conn.path.windows(2) {
has = true;
if let Some(e) = charged(conn, s[0], s[1], &cap, gw, gh) {
if occ[e] > cap[e] {
over = true;
break;
}
}
}
if has && !over {
continue;
}
changed += 1;
for s in conn.path.windows(2) {
if let Some(e) = charged(conn, s[0], s[1], &cap, gw, gh) {
occ[e] -= conn.demand;
}
}
// A* over global cells. Crossing a boundary costs its length, times history, times the
// present cost of being over capacity.
tick += 1;
heap.clear();
let idx = |i: i64, j: i64, l: usize| (l * gh + j as usize) * gw + i as usize;
let inb = |i: i64, j: i64| i >= 0 && j >= 0 && (i as usize) < gw && (j as usize) < gh;
let (ta, tb) = (conn.b.0, conn.b.1);
let est = |i: i64, j: i64| ((i - ta).abs() + (j - tb).abs()) as f32 * base;
for &l in &conn.a.2 {
if inb(conn.a.0, conn.a.1) {
let s = idx(conn.a.0, conn.a.1, l);
best[s] = 0.0;
stamp[s] = tick;
prev[s] = u32::MAX;
heap.push(Reverse(((est(conn.a.0, conn.a.1) / 100.0) as u32, s as u32)));
}
}
let mut goal = None;
while let Some(Reverse((_, s))) = heap.pop() {
let s = s as usize;
let (i, j, l) = ((s % gw) as i64, ((s / gw) % gh) as i64, s / (gw * gh));
if i == ta && j == tb && conn.b.2.contains(&l) {
goal = Some(s);
break;
}
let gs = best[s];
let mult = if c.route[l] == 0 { 1.0 } else { r.cost_bottom as f32 };
for (di, dj) in [(1i64, 0i64), (-1, 0), (0, 1), (0, -1)] {
let (a, b) = (i + di, j + dj);
if !inb(a, b) {
continue;
}
let e = step_edge((i, j, l), (a, b, l), gw, gh).unwrap();
let over_by = (occ[e] + conn.demand - cap[e]).max(0.0);
// A pin's own cell boundary reads closed to everyone (its pads block the map),
// so it costs no penalty for the connection that starts or ends there.
let at_end = (i == conn.a.0 && j == conn.a.1) || (i == ta && j == tb) || (a == conn.a.0 && b == conn.a.1) || (a == ta && b == tb);
let cong = if at_end && cap[e] < conn.demand { 1.0 } else { hist[e] * (1.0 + pres * over_by) };
let n = gs + base * mult * cong;
let t = idx(a, b, l);
if stamp[t] != tick || n < best[t] {
stamp[t] = tick;
best[t] = n;
prev[t] = s as u32;
heap.push(Reverse((((n + est(a, b)) / 100.0) as u32, t as u32)));
}
}
for o in 0..nl {
if o != l {
let t = idx(i, j, o);
let n = gs + via_cost;
if stamp[t] != tick || n < best[t] {
stamp[t] = tick;
best[t] = n;
prev[t] = s as u32;
heap.push(Reverse(((n + est(i, j)) as u32 / 100, t as u32)));
}
}
}
}
conn.path.clear();
if let Some(mut s) = goal {
loop {
conn.path.push(((s % gw) as i64, ((s / gw) % gh) as i64, s / (gw * gh)));
if prev[s] == u32::MAX {
break;
}
s = prev[s] as usize;
}
conn.path.reverse();
}
for s in conn.path.windows(2) {
if let Some(e) = charged(conn, s[0], s[1], &cap, gw, gh) {
occ[e] += conn.demand;
}
}
}
let mut over_total = 0.0;
for e in 0..ne {
let o = (occ[e] - cap[e]).max(0.0);
over_total += o;
hist[e] += ACC_FAC * o;
}
if round == 0 {
first_over = over_total;
}
last_over = over_total;
pres *= PRES_MULT;
// No wall-clock cut-off (clock.rs): the rounds are bounded by ROUNDS, the same on every run.
if changed == 0 {
break;
}
}
// Corridors: the planned cells, a cell wider each way, so the search has room to choose.
for conn in &conns {
if conn.path.is_empty() {
continue;
}
let (mut i0, mut j0, mut i1, mut j1) = (i64::MAX, i64::MAX, i64::MIN, i64::MIN);
for s in &conn.path {
i0 = i0.min(s.0 - 1);
j0 = j0.min(s.1 - 1);
i1 = i1.max(s.0 + 1);
j1 = j1.max(s.1 + 1);
}
let (cw, ch) = ((i1 - i0 + 1) as usize, (j1 - j0 + 1) as usize);
let mut on = vec![false; cw * ch * nl];
for s in &conn.path {
for dj in -1..=1i64 {
for di in -1..=1i64 {
on[(s.2 * ch + (s.1 + dj - j0) as usize) * cw + (s.0 + di - i0) as usize] = true;
}
}
}
let bbox = (c.x0 + i0 as f64 * g, c.y0 + j0 as f64 * g, c.x0 + (i1 + 1) as f64 * g, c.y0 + (j1 + 1) as f64 * g);
out.corridors.insert(conn.key, Rc::new(Corridor { bbox, on, i0, j0, gw: cw, gh: ch, f: f as i64, nl }));
}
out.note = format!("global: {} connections, {} rounds, overuse {:.0} -> {:.0}, {:.0} ms", conns.len(), rounds, first_over, last_over, t0.elapsed().as_secs_f64() * 1000.0);
guide.plan = Some(out.clone());
out
}
/// The boundary a step uses up. A pin's own cell boundary reads closed to everyone, since its pads
/// block the map, so a step off a terminal over such a boundary uses nothing up.
#[inline]
fn charged(conn: &Conn, a: (i64, i64, usize), b: (i64, i64, usize), cap: &[f32], gw: usize, gh: usize) -> Option<usize> {
let e = step_edge(a, b, gw, gh)?;
let end = |p: (i64, i64, usize)| (p.0 == conn.a.0 && p.1 == conn.a.1) || (p.0 == conn.b.0 && p.1 == conn.b.1);
if cap[e] < conn.demand && (end(a) || end(b)) {
return None;
}
Some(e)
}
/// Would these two connections cross if each ran straight? The estimate buses use. Coarse on purpose:
/// which of two lines must give way depends on the ends, not on the detail of the route between.
pub fn chords_cross(a: V, b: V, c: V, d: V) -> bool {
seg_cross(a, b, c, d)
}
/// The boundary between two neighbouring global cells on one layer; None for a via.
#[inline]
fn step_edge(a: (i64, i64, usize), b: (i64, i64, usize), gw: usize, gh: usize) -> Option<usize> {
if a.2 != b.2 {
return None;
}
let (d, i, j) = if a.1 == b.1 { (0, a.0.min(b.0), a.1) } else { (1, a.0, a.1.min(b.1)) };
Some(((a.2 * 2 + d) * gh + j as usize) * gw + i as usize)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::router::build_world;
use crate::search::{search, Profile};
fn board_text(extra: &str) -> String {
format!(
r#"(kicad_pcb (version 20240108) (generator test)
(layers (0 "F.Cu" signal) (31 "B.Cu" signal) (44 "Edge.Cuts" user))
(net 0 "") (net 1 "A") (net 2 "B")
(gr_rect (start 0 0) (end 20 10) (layer "Edge.Cuts") (width 0.1))
{extra}
)"#
)
}
fn term(x: f64, y: f64) -> [Terminal; 1] {
[Terminal::Point { p: (mm(x), mm(y)), layers: vec![0] }]
}
fn profile(r: &Rules, vias: u32) -> Profile {
Profile { width: r.neck, neck: r.neck, clearance: r.clearance, target_gap: r.clearance, max_vias: vias, margin: 40.0 * NM, layer_bias: None }
}
fn query<'a>(r: &'a Rules, s: &'a [Terminal], t: &'a [Terminal], vias: u32) -> Query<'a> {
Query { net: 99, also: None, sources: s, targets: t, profile: profile(r, vias), rules: r, soft: None, via_in: None, history: None, free: &[], arrive: None }
}
/// A wall of two tracks with a gap that a neck-width track fits exactly. Rounding of any kind
/// would close this lane; the map must not. A tenth of a millimetre less must close it.
#[test]
fn a_lane_that_exactly_fits_stays_open() {
let r = Rules::default();
let gap = (r.neck + 2 * r.clearance) as f64 / NM;
let half = r.track as f64 / NM / 2.0;
let (a, b) = (5.0 - gap / 2.0 - half, 5.0 + gap / 2.0 + half);
let extra = format!(
r#"(segment (start 10 0.5) (end 10 {a}) (width 0.2) (layer "F.Cu") (net 2))
(segment (start 10 {b}) (end 10 9.5) (width 0.2) (layer "F.Cu") (net 2))"#
);
let board = Board::parse(board_text(&extra)).unwrap();
let w = build_world(&board, &r);
let (s, t) = (term(5.0, 5.0), term(15.0, 5.0));
let q = query(&r, &s, &t, 0);
let mut guide = w.guide.borrow_mut();
let tb = guide.prepare(&w, &q, (0.0, 0.0, 20.0 * NM, 10.0 * NM)).unwrap();
assert!(guide.cost(tb, &w, &r, V(5.0 * NM, 5.0 * NM), 0).is_finite());
drop(guide);
assert!(search(&w, &q).is_some(), "the detailed search should use the lane too");
let (a, b) = (a + 0.05, b - 0.05);
let extra = format!(
r#"(segment (start 10 0.5) (end 10 {a}) (width 0.2) (layer "F.Cu") (net 2))
(segment (start 10 {b}) (end 10 9.5) (width 0.2) (layer "F.Cu") (net 2))"#
);
let board = Board::parse(board_text(&extra)).unwrap();
let w = build_world(&board, &r);
let q = query(&r, &s, &t, 0);
assert!(search(&w, &q).is_none(), "the lane is 0.1 mm too narrow");
}
/// The same wall with no lane at all is a proof: no nodes spent.
#[test]
fn a_closed_wall_is_proved_without_searching() {
let r = Rules::default();
let extra = r#"(segment (start 10 0) (end 10 10) (width 0.2) (layer "F.Cu") (net 2))"#;
let board = Board::parse(board_text(extra)).unwrap();
let w = build_world(&board, &r);
let (s, t) = (term(5.0, 5.0), term(15.0, 5.0));
let q = query(&r, &s, &t, 0);
assert!(search(&w, &q).is_none());
let g = w.guide.borrow();
assert_eq!(g.closed_early, 1);
assert_eq!(g.nodes_failed, 0);
}
/// A committed route closes the map and taking it away opens it again.
#[test]
fn the_map_follows_commits_and_rips() {
let r = Rules::default();
let board = Board::parse(board_text("")).unwrap();
let mut w = build_world(&board, &r);
let (s, t) = (term(5.0, 5.0), term(15.0, 5.0));
assert!(search(&w, &query(&r, &s, &t, 0)).is_some());
let id = w.layers[0].add(Shape::Capsule { a: V(10.0 * NM, 0.0), b: V(10.0 * NM, 10.0 * NM), r: 0.1 * NM }, 2, Owner::Conn(7));
assert!(search(&w, &query(&r, &s, &t, 0)).is_none());
assert_eq!(w.guide.borrow().closed_early, 1);
w.layers[0].remove(id);
assert!(search(&w, &query(&r, &s, &t, 0)).is_some());
}
/// The hard case the map must not get wrong: every pin of the 0.4 mm QFN on PiProbe with its
/// copper stripped. The designer routes all of these; "no path" here would be the map's mistake.
#[test]
fn piprobe_u1_escapes_are_open() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/bench/boards/piprobe_v13.ref.kicad_pcb");
if !std::path::Path::new(&path).exists() {
eprintln!("skipped: {path} is not here (the reference boards do not ship with the source)");
return;
}
let mut board = Board::read(path).unwrap();
board.tracks.clear();
board.vias.clear();
let mut r = Rules::default();
crate::project::apply(path, &mut r);
let w = build_world(&board, &r);
let u1 = board.footprints.iter().find(|f| f.pads.iter().any(|&p| board.pad_name(p).starts_with("U1.")));
let u1 = u1.expect("U1 on the board");
let mut guide = w.guide.borrow_mut();
guide.coarse = Some(Coarse::build(&w, &r));
let c = guide.coarse.as_ref().unwrap();
let mut checked = 0;
for &i in &u1.pads {
let pad = &board.pads[i];
if pad.net == NO_NET || pad.layers.is_empty() {
continue;
}
let Some(&other) = (0..board.pads.len()).collect::<Vec<_>>().iter().find(|&&o| o != i && board.pads[o].net == pad.net && !u1.pads.contains(&o)) else { continue };
let layers: Vec<usize> = pad.layers.iter().copied().filter(|l| w.route_layers.contains(l)).collect();
let s = [Terminal::Point { p: pad.center, layers: layers.clone() }];
let t = [Terminal::Point { p: board.pads[other].center, layers: board.pads[other].layers.iter().copied().filter(|l| w.route_layers.contains(l)).collect() }];
let q = Query { net: pad.net, also: None, sources: &s, targets: &t, profile: profile(&r, 2), rules: &r, soft: None, via_in: None, history: None, free: &[], arrive: None };
assert!(!walled_in(c, &w, &q, pad.center, &layers, &t), "{} called walled in", board.pad_name(i));
checked += 1;
}
assert!(checked >= 20, "only {checked} U1 pins checked");
drop(guide);
for &i in &u1.pads {
let pad = &board.pads[i];
let Some(&other) = (0..board.pads.len()).collect::<Vec<_>>().iter().find(|&&o| o != i && pad.net != NO_NET && board.pads[o].net == pad.net && !u1.pads.contains(&o)) else { continue };
let layers: Vec<usize> = pad.layers.iter().copied().filter(|l| w.route_layers.contains(l)).collect();
if layers.is_empty() {
continue;
}
let s = [Terminal::Point { p: pad.center, layers }];
let t = [Terminal::Point { p: board.pads[other].center, layers: board.pads[other].layers.iter().copied().filter(|l| w.route_layers.contains(l)).collect() }];
let q = Query { net: pad.net, also: None, sources: &s, targets: &t, profile: profile(&r, 2), rules: &r, soft: None, via_in: None, history: None, free: &[], arrive: None };
let mut g = w.guide.borrow_mut();
let tb = g.prepare(&w, &q, (-1e12, -1e12, 1e12, 1e12)).unwrap();
assert!(g.cost(tb, &w, &r, pad.center.into(), pad.layers[0]).is_finite(), "{} closed on the coarse map", board.pad_name(i));
}
}
}