app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
main
John Lauer
Publish 0.2.25
68f933c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
//! `route --variants N` (issue #55): the levers one variant pulls, chosen in a fixed order so the
//! same board, spec and N always give the same variants. The levers are the ones the
//! kicad-place-route-loop skill lists: the signal-net order, the via size (within the spec's own
//! sizes), the pass count and a seed for a shuffled order. Variant 1 is always a plain `route`.
use serde_json::{json, Value};
use crate::{NetState, Rules};
/// The most variants one `route --variants` runs.
pub const MAX_VARIANTS: usize = 8;
/// The order the signal nets are routed in (planes and stubs always go first).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum NetOrder {
/// Shortest spanning length first: the router's own order and a plain `route`.
#[default]
Shortest,
/// Longest first: the long nets take the open corridors before the short ones box them in.
Longest,
/// Most pads (clusters) first, shortest first among equals.
Fanout,
/// A fixed shuffle keyed by the seed and the net name.
Seeded(u64),
}
impl NetOrder {
pub fn describe(&self) -> String {
match self {
NetOrder::Shortest => "shortest first".into(),
NetOrder::Longest => "longest first".into(),
NetOrder::Fanout => "most pads first".into(),
NetOrder::Seeded(s) => format!("seeded order (seed {s})"),
}
}
pub fn name(&self) -> &'static str {
match self {
NetOrder::Shortest => "shortest-first",
NetOrder::Longest => "longest-first",
NetOrder::Fanout => "fanout-first",
NetOrder::Seeded(_) => "seeded",
}
}
}
/// The via the signal nets take.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ViaChoice {
/// The spec's sizes: signal nets the signal via, power and wide nets the power via.
Spec,
/// Signal nets take the spec's power via too (a larger hole, still a size the spec allows).
PowerSize,
}
/// One variant's levers.
#[derive(Clone, Debug, PartialEq)]
pub struct Levers {
pub order: NetOrder,
pub via: ViaChoice,
pub passes: usize,
}
impl Levers {
/// A plain `route --passes P`.
pub fn baseline(passes: usize) -> Levers {
Levers { order: NetOrder::Shortest, via: ViaChoice::Spec, passes }
}
/// The rules this variant routes to: the spec's, with the via lever applied.
pub fn apply(&self, rules: &Rules) -> Rules {
let mut r = rules.clone();
if self.via == ViaChoice::PowerSize {
r.via_small = r.via;
r.drill_small = r.drill;
}
r
}
pub fn to_json(&self, rules: &Rules) -> Value {
let r = self.apply(rules);
json!({
"order": self.order.name(),
"seed": match self.order { NetOrder::Seeded(s) => json!(s), _ => Value::Null },
"via": match self.via { ViaChoice::Spec => "spec", ViaChoice::PowerSize => "power-size" },
"signalVia": [r.via_small, r.drill_small],
"powerVia": [r.via, r.drill],
"passes": self.passes,
})
}
/// One short line for the table.
pub fn label(&self) -> String {
let via = match self.via { ViaChoice::Spec => "spec vias", ViaChoice::PowerSize => "power-size vias" };
format!("{}, {via}, {} passes", self.order.describe(), self.passes)
}
}
/// The first `n` variants (at most MAX_VARIANTS) for a run whose plain route takes `passes`.
/// The order is fixed; a via variant is left out when the spec's signal and power vias are the
/// same size (it would route the same copper), and seeded orders fill the list up to `n`.
pub fn plan(n: usize, passes: usize, rules: &Rules) -> Vec<Levers> {
let n = n.clamp(1, MAX_VARIANTS);
let same_via = (rules.via - rules.via_small).abs() < 1e-9 && (rules.drill - rules.drill_small).abs() < 1e-9;
let l = |order, via, passes| Levers { order, via, passes };
let mut out: Vec<Levers> = [
l(NetOrder::Shortest, ViaChoice::Spec, passes),
l(NetOrder::Longest, ViaChoice::Spec, passes),
l(NetOrder::Shortest, ViaChoice::PowerSize, passes),
l(NetOrder::Fanout, ViaChoice::Spec, passes),
l(NetOrder::Seeded(1), ViaChoice::Spec, passes),
l(NetOrder::Seeded(2), ViaChoice::Spec, passes),
l(NetOrder::Shortest, ViaChoice::Spec, passes + 4),
l(NetOrder::Longest, ViaChoice::PowerSize, passes + 4),
]
.into_iter()
.filter(|v| !(same_via && v.via == ViaChoice::PowerSize))
.collect();
let mut seed = 3;
while out.len() < n {
out.push(l(NetOrder::Seeded(seed), ViaChoice::Spec, passes));
seed += 1;
}
out.truncate(n);
out
}
/// FNV-1a of the name, mixed with the seed (splitmix64): the same key on every machine.
fn key(name: &str, seed: u64) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in name.bytes() {
h ^= b as u64;
h = h.wrapping_mul(0x0100_0000_01b3);
}
let mut z = h ^ seed.wrapping_mul(0x9e37_79b9_7f4a_7c15);
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
/// Reorder the signal nets (already sorted shortest first) for `order`; Shortest leaves them as they are.
pub fn reorder(signal: &mut [usize], nets: &[NetState], order: NetOrder) {
match order {
NetOrder::Shortest => {}
NetOrder::Longest => signal.sort_by(|a, b| nets[*b].mst_len.partial_cmp(&nets[*a].mst_len).unwrap_or(std::cmp::Ordering::Equal)),
NetOrder::Fanout => signal.sort_by_key(|&n| std::cmp::Reverse(nets[n].clusters.len())),
NetOrder::Seeded(s) => signal.sort_by_key(|&n| (key(&nets[n].name, s), nets[n].name.clone())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plan_is_fixed_capped_and_starts_with_a_plain_route() {
let r = Rules::default();
let a = plan(3, 5, &r);
assert_eq!(a, plan(3, 5, &r));
assert_eq!(a[0], Levers::baseline(5));
assert_eq!(a[1].order, NetOrder::Longest);
assert_eq!(a[2].via, ViaChoice::PowerSize);
assert_eq!(plan(50, 5, &r).len(), MAX_VARIANTS);
assert_eq!(plan(0, 5, &r).len(), 1);
}
#[test]
fn same_vias_drop_the_via_variant_and_seeds_fill_in() {
let mut r = Rules::default();
r.via_small = r.via;
r.drill_small = r.drill;
let a = plan(8, 5, &r);
assert_eq!(a.len(), 8);
assert!(a.iter().all(|v| v.via == ViaChoice::Spec));
assert_eq!((a[6].order, a[7].order), (NetOrder::Seeded(3), NetOrder::Seeded(4)));
}
#[test]
fn power_size_via_applies_to_signal_nets_only_in_the_rules() {
let r = Rules::default();
let v = Levers { order: NetOrder::Shortest, via: ViaChoice::PowerSize, passes: 5 }.apply(&r);
assert_eq!((v.via_small, v.drill_small), (r.via, r.drill));
assert_eq!(Levers::baseline(5).apply(&r).via_small, r.via_small);
}
#[test]
fn seeded_keys_are_stable_and_differ_by_seed() {
assert_eq!(key("GND", 1), key("GND", 1));
assert_ne!(key("GND", 1), key("GND", 2));
let names = ["A", "B", "C", "D", "E", "F"];
let o1: Vec<u64> = names.iter().map(|n| key(n, 1)).collect();
let o2: Vec<u64> = names.iter().map(|n| key(n, 2)).collect();
let rank = |o: &Vec<u64>| { let mut i: Vec<usize> = (0..o.len()).collect(); i.sort_by_key(|&k| o[k]); i };
assert_ne!(rank(&o1), rank(&o2));
}
#[test]
fn the_order_lever_changes_which_net_goes_first_and_default_is_unchanged() {
let pads = [("J1", "A", 2.0, 2.0, true), ("J2", "A", 5.0, 2.0, true), ("J3", "B", 2.0, 8.0, true), ("J4", "B", 18.0, 8.0, true)];
let first = |order: NetOrder| {
let b = crate::board_copper::tests::board(&pads, "");
let mut r = crate::Router::new(b, Rules::default(), false);
r.order = order;
let (_, _, failed) = crate::drive::route_board(&mut r, 2, 8, 400);
assert_eq!(failed, 0);
r.nets.iter().find(|n| n.routed_order == Some(1)).map(|n| n.name.clone()).unwrap()
};
assert_eq!(first(NetOrder::default()), "A");
assert_eq!(first(NetOrder::Shortest), "A");
assert_eq!(first(NetOrder::Longest), "B");
}
}