feat(autorouter/ratsnest): Have ratlines across layers

This commit is contained in:
Mikolaj Wielgus 2025-09-23 13:25:32 +02:00
parent d74a06b2ea
commit a4a000feb9
6 changed files with 163 additions and 52 deletions

View File

@ -73,7 +73,7 @@ pub struct Autorouter<M> {
impl<M: AccessMesadata> Autorouter<M> {
pub fn new(board: Board<M>) -> Result<Self, InsertionError> {
let ratsnest = Ratsnest::new(board.layout())?;
let ratsnest = Ratsnest::new(&board)?;
Ok(Self { board, ratsnest })
}

View File

@ -2,51 +2,149 @@
//
// SPDX-License-Identifier: MIT
use std::collections::BTreeSet;
use derive_getters::Getters;
use petgraph::unionfind::UnionFind;
use specctra_core::rules::AccessRules;
use specctra_core::mesadata::AccessMesadata;
use crate::{
drawing::{graph::PrimitiveIndex, primitive::GetJoints},
board::Board,
drawing::{dot::FixedDotIndex, graph::PrimitiveIndex, primitive::GetJoints},
geometry::GenericNode,
graph::GetIndex,
layout::Layout,
};
#[derive(Clone, Getters)]
pub struct Conncomps {
pub struct ConncompsWithPrincipalLayer {
unionfind: UnionFind<usize>,
}
impl Conncomps {
pub fn new(layout: &Layout<impl AccessRules>) -> Self {
let mut unionfind = UnionFind::new(layout.drawing().geometry().dot_index_bound());
impl ConncompsWithPrincipalLayer {
pub fn new(board: &Board<impl AccessMesadata>, principal_layer: usize) -> Self {
let mut principally_visited_pins = BTreeSet::new();
let mut unionfind = UnionFind::new(board.layout().drawing().geometry().dot_index_bound());
for primitive in layout.drawing().primitive_nodes() {
match primitive {
PrimitiveIndex::FixedSeg(seg) => {
let joints = layout.drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
for node in board
.layout()
.drawing()
.layer_primitive_nodes(principal_layer)
{
Self::unionize_primitive_endpoint_dots(board, &mut unionfind, node);
if let Some(pinname) = board.node_pinname(&GenericNode::Primitive(node)) {
principally_visited_pins.insert(pinname.clone());
}
}
/*for layer in 0..board.layout().drawing().layer_count() {
if layer != principal_layer {
for primitive in board.layout().drawing().layer_primitive_nodes(layer) {
if let Some(pinname) = board.node_pinname(&GenericNode::Primitive(primitive)) {
if !principally_visited_pins.contains(pinname) {
Self::unionize_by_primitive(board, &mut unionfind, primitive);
}
}
}
PrimitiveIndex::LoneLooseSeg(seg) => {
let joints = layout.drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
}*/
//TODO for pinname in board.pins() if !principally_visited_pins.contains(pinname) for node in board.pinname_nodes() unionize to first found element
for pinname in board.pinnames() {
if principally_visited_pins.contains(pinname) {
let mut iter = board.pinname_nodes(pinname);
let Some(first_fixed_dot) = iter.find_map(|node| {
if let GenericNode::Primitive(PrimitiveIndex::FixedDot(first_fixed_dot)) = node
{
Some(first_fixed_dot)
} else {
None
}
}) else {
continue;
};
for node in board.pinname_nodes(pinname) {
if let GenericNode::Primitive(primitive) = node {
Self::unionize_to_common(board, &mut unionfind, primitive, first_fixed_dot);
}
}
PrimitiveIndex::SeqLooseSeg(seg) => {
let joints = layout.drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::FixedBend(bend) => {
let joints = layout.drawing().primitive(bend).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::LooseBend(bend) => {
let joints = layout.drawing().primitive(bend).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
_ => (),
}
}
Self { unionfind }
}
fn unionize_primitive_endpoint_dots(
board: &Board<impl AccessMesadata>,
unionfind: &mut UnionFind<usize>,
primitive: PrimitiveIndex,
) {
match primitive {
PrimitiveIndex::FixedSeg(seg) => {
let joints = board.layout().drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::LoneLooseSeg(seg) => {
let joints = board.layout().drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::SeqLooseSeg(seg) => {
let joints = board.layout().drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::FixedBend(bend) => {
let joints = board.layout().drawing().primitive(bend).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::LooseBend(bend) => {
let joints = board.layout().drawing().primitive(bend).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
_ => (),
}
}
fn unionize_to_common(
board: &Board<impl AccessMesadata>,
unionfind: &mut UnionFind<usize>,
primitive: PrimitiveIndex,
common: FixedDotIndex,
) {
match primitive {
PrimitiveIndex::FixedDot(dot) => {
unionfind.union(common.index(), dot.index());
}
PrimitiveIndex::LooseDot(dot) => {
unionfind.union(common.index(), dot.index());
}
PrimitiveIndex::FixedSeg(seg) => {
let joints = board.layout().drawing().primitive(seg).joints();
unionfind.union(common.index(), joints.0.index());
unionfind.union(common.index(), joints.1.index());
}
PrimitiveIndex::LoneLooseSeg(seg) => {
let joints = board.layout().drawing().primitive(seg).joints();
unionfind.union(common.index(), joints.0.index());
unionfind.union(common.index(), joints.1.index());
}
PrimitiveIndex::SeqLooseSeg(seg) => {
let joints = board.layout().drawing().primitive(seg).joints();
unionfind.union(common.index(), joints.0.index());
unionfind.union(common.index(), joints.1.index());
}
PrimitiveIndex::FixedBend(bend) => {
let joints = board.layout().drawing().primitive(bend).joints();
unionfind.union(common.index(), joints.0.index());
unionfind.union(common.index(), joints.1.index());
}
PrimitiveIndex::LooseBend(bend) => {
let joints = board.layout().drawing().primitive(bend).joints();
unionfind.union(common.index(), joints.0.index());
unionfind.union(common.index(), joints.1.index());
}
_ => (),
}
}
}

View File

@ -11,22 +11,20 @@ use enum_dispatch::enum_dispatch;
use geo::Point;
use petgraph::{data::Element, prelude::StableUnGraph};
use spade::{handles::FixedVertexHandle, HasPosition, InsertionError, Point2};
use specctra_core::mesadata::AccessMesadata;
use crate::{
autorouter::conncomps::Conncomps,
autorouter::conncomps::ConncompsWithPrincipalLayer,
board::Board,
drawing::{
band::BandTermsegIndex,
dot::FixedDotIndex,
graph::{GetMaybeNet, MakePrimitiveRef, PrimitiveIndex},
primitive::MakePrimitiveShape,
rules::AccessRules,
},
geometry::shape::AccessShape,
graph::{GenericIndex, GetIndex, MakeRef},
layout::{
poly::{MakePolygon, PolyWeight},
Layout,
},
layout::poly::{MakePolygon, PolyWeight},
triangulation::{GetTrianvertexNodeIndex, Triangulation},
};
@ -107,8 +105,8 @@ pub struct Ratsnest {
}
impl Ratsnest {
pub fn new(layout: &Layout<impl AccessRules>) -> Result<Self, InsertionError> {
let conncomps = Conncomps::new(layout);
pub fn new(board: &Board<impl AccessMesadata>) -> Result<Self, InsertionError> {
let conncomps = ConncompsWithPrincipalLayer::new(board, 0);
let mut this = Self {
graph: StableUnGraph::default(),
@ -116,16 +114,16 @@ impl Ratsnest {
let mut triangulations = BTreeMap::new();
for layer in 0..layout.drawing().layer_count() {
for layer in 0..board.layout().drawing().layer_count() {
let mut handle_ratvertex_weight =
|maybe_net: Option<usize>, vertex: RatvertexIndex, pos: Point| {
if let Some(net) = maybe_net {
triangulations
.entry((layer, net))
.entry(net)
.or_insert_with(|| {
Triangulation::new(RatvertexToHandleMap::new(
layout.drawing().geometry().dot_index_bound(),
layout.drawing().geometry().compound_index_bound(),
board.layout().drawing().geometry().dot_index_bound(),
board.layout().drawing().geometry().compound_index_bound(),
))
})
.add_vertex(RatvertexWeight { vertex, pos })?;
@ -133,30 +131,36 @@ impl Ratsnest {
Ok(())
};
for node in layout.drawing().layer_primitive_nodes(layer) {
for node in board.layout().drawing().layer_primitive_nodes(layer) {
if let PrimitiveIndex::FixedDot(dot) = node {
// Dots that are parts of polys are ignored because ratlines
// should only go to their centerpoints.
if layout.drawing().compounds(dot).next().is_none() {
if board.layout().drawing().compounds(dot).next().is_none() {
handle_ratvertex_weight(
layout.drawing().primitive(dot).maybe_net(),
board.layout().drawing().primitive(dot).maybe_net(),
RatvertexIndex::FixedDot(dot),
node.primitive_ref(layout.drawing()).shape().center(),
node.primitive_ref(board.layout().drawing())
.shape()
.center(),
)?;
}
}
}
for poly in layout.layer_poly_nodes(layer) {
for poly in board.layout().layer_poly_nodes(layer) {
handle_ratvertex_weight(
layout.drawing().compound_weight(poly.into()).maybe_net(),
board
.layout()
.drawing()
.compound_weight(poly.into())
.maybe_net(),
RatvertexIndex::Poly(poly),
poly.ref_(layout).shape().center(),
poly.ref_(board.layout()).shape().center(),
)?;
}
}
for ((_layer, _net), triangulation) in triangulations {
for (_net, triangulation) in triangulations {
let mut map = Vec::new();
for element in petgraph::algo::min_spanning_tree(&triangulation) {

View File

@ -97,4 +97,8 @@ impl<K: Eq + Ord + Clone, V: Eq + Ord + Clone> BiBTreeMapSet<K, V> {
None
}
}
pub fn keys(&self) -> impl Iterator<Item = &K> + '_ {
self.key_to_values.keys()
}
}

View File

@ -180,6 +180,11 @@ impl<M: AccessMesadata> Board<M> {
poly
}
/// Returns an iterator over all the pin names.
pub fn pinnames(&self) -> impl Iterator<Item = &String> + '_ {
self.pinname_nodes.keys()
}
/// Returns an iterator over the set of all nodes associated with a given
/// pin name.
pub fn pinname_nodes(&self, pinname: &str) -> impl Iterator<Item = NodeIndex> + '_ {
@ -215,7 +220,7 @@ impl<M: AccessMesadata> Board<M> {
self.band_bandname.get_by_right(bandname)
}
/// Creates band between the two nodes
/// Registers that a band is between the two nodes.
pub fn try_set_band_between_nodes(
&mut self,
recorder: &mut BoardDataEdit,

View File

@ -6,7 +6,7 @@ use std::{fs::File, io::BufReader};
use topola::{
autorouter::{
conncomps::Conncomps,
conncomps::ConncompsWithPrincipalLayer,
history::{History, HistoryError},
invoker::{Invoker, InvokerError},
Autorouter,
@ -174,7 +174,7 @@ pub fn assert_that_all_single_layer_groundless_ratlines_are_autorouted(
autorouter: &mut Autorouter<impl AccessMesadata>,
layername: &str,
) {
let conncomps = Conncomps::new(autorouter.board().layout());
let conncomps = ConncompsWithPrincipalLayer::new(autorouter.board().layout());
for ratline in autorouter.ratsnest().graph().edge_indices() {
let (origin_dot, destination_dot) = ratline.ref_(autorouter).endpoint_dots();