diff --git a/src/autorouter/conncomps.rs b/src/autorouter/conncomps.rs index e672597..2b34bfe 100644 --- a/src/autorouter/conncomps.rs +++ b/src/autorouter/conncomps.rs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Topola contributors // // SPDX-License-Identifier: MIT - use std::collections::BTreeSet; use derive_getters::Getters; @@ -16,12 +15,32 @@ use crate::{ }; #[derive(Clone, Getters)] -pub struct ConncompsWithPrincipalLayer { +pub struct Conncomps { unionfind: UnionFind, } -impl ConncompsWithPrincipalLayer { - pub fn new(board: &Board, principal_layer: usize) -> Self { +impl Conncomps { + pub fn new(board: &Board) -> Self { + let mut unionfind = UnionFind::new(board.layout().drawing().geometry().dot_index_bound()); + + for node in board.layout().drawing().primitive_nodes() { + Self::unionize_primitive_endpoint_dots(board, &mut unionfind, node); + } + + // Pins can have padstacks that span multiple layers. To account for + // that, we have another loop to go over all the pins and connect all + // their primitives. + for pinname in board.pinnames() { + Self::unionize_pin(board, &mut unionfind, pinname); + } + + Self { unionfind } + } + + pub fn new_with_principal_layer( + board: &Board, + principal_layer: usize, + ) -> Self { let mut principally_visited_pins = BTreeSet::new(); let mut unionfind = UnionFind::new(board.layout().drawing().geometry().dot_index_bound()); @@ -42,23 +61,7 @@ impl ConncompsWithPrincipalLayer { // their primitives. 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); - } - } + Self::unionize_pin(board, &mut unionfind, pinname); } } @@ -99,6 +102,29 @@ impl ConncompsWithPrincipalLayer { } } + fn unionize_pin( + board: &Board, + unionfind: &mut UnionFind, + pinname: &str, + ) { + 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 { + return; + }; + + for node in board.pinname_nodes(pinname) { + if let GenericNode::Primitive(primitive) = node { + Self::unionize_to_common(board, unionfind, primitive, first_fixed_dot); + } + } + } + fn unionize_to_common( board: &Board, unionfind: &mut UnionFind, diff --git a/src/autorouter/ratline.rs b/src/autorouter/ratline.rs index 764bcce..d680d28 100644 --- a/src/autorouter/ratline.rs +++ b/src/autorouter/ratline.rs @@ -7,6 +7,7 @@ use petgraph::graph::{EdgeIndex, NodeIndex}; use specctra_core::mesadata::AccessMesadata; use crate::{ + autorouter::{ratsnest::RatvertexNodeIndex, Autorouter}, drawing::{ band::BandTermsegIndex, dot::FixedDotIndex, @@ -17,8 +18,6 @@ use crate::{ triangulation::GetTrianvertexNodeIndex, }; -use super::{ratsnest::RatvertexNodeIndex, Autorouter}; - #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct RatlineUid { pub principal_layer: usize, diff --git a/src/autorouter/ratsnest.rs b/src/autorouter/ratsnest.rs index 22886bb..a05b2e9 100644 --- a/src/autorouter/ratsnest.rs +++ b/src/autorouter/ratsnest.rs @@ -18,6 +18,7 @@ use spade::{handles::FixedVertexHandle, HasPosition, InsertionError, Point2}; use specctra_core::mesadata::AccessMesadata; use crate::{ + autorouter::conncomps::Conncomps, board::Board, drawing::{ band::BandTermsegIndex, @@ -31,7 +32,7 @@ use crate::{ triangulation::{GetTrianvertexNodeIndex, Triangulation}, }; -use super::{conncomps::ConncompsWithPrincipalLayer, ratline::RatlineWeight}; +use super::ratline::RatlineWeight; #[enum_dispatch(GetIndex)] #[derive(Debug, Clone, Copy, PartialEq)] @@ -113,7 +114,7 @@ impl Ratsnest { board: &Board, principal_layer: usize, ) -> Result { - let conncomps = ConncompsWithPrincipalLayer::new(board, principal_layer); + let conncomps = Conncomps::new_with_principal_layer(board, principal_layer); let mut this = Self { graph: StableUnGraph::default(), diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 788578a..2d88848 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -6,7 +6,7 @@ use std::{fs::File, io::BufReader}; use topola::{ autorouter::{ - conncomps::ConncompsWithPrincipalLayer, + conncomps::Conncomps, history::{History, HistoryError}, invoker::{Invoker, InvokerError}, ratline::RatlineUid, @@ -176,87 +176,49 @@ pub fn assert_layer_0_navnode_count( assert_eq!(navmesh.graph().node_count(), expected_count); } -pub fn assert_that_all_single_layer_groundless_ratlines_are_autorouted( +pub fn assert_that_all_ratlines_besides_gnd_are_autorouted( autorouter: &mut Autorouter, - layername: &str, ) { - let layer = autorouter - .board() - .layout() - .rules() - .layername_layer(layername) - .unwrap(); - let conncomps = ConncompsWithPrincipalLayer::new(autorouter.board(), layer); + let conncomps = Conncomps::new(autorouter.board()); + assert!(autorouter.board().layout().drawing().layer_count() >= 1); - for ratline in autorouter - .ratsnests() - .on_principal_layer(layer) - .graph() - .edge_indices() - .map(|index| RatlineUid { - principal_layer: layer, - index, - }) - { - let (origin_dot, destination_dot) = ratline.ref_(autorouter).endpoint_dots(); + for principal_layer in 0..autorouter.board().layout().drawing().layer_count() { + for ratline in autorouter + .ratsnests() + .on_principal_layer(principal_layer) + .graph() + .edge_indices() + .map(|index| RatlineUid { + principal_layer, + index, + }) + { + let (origin_dot, destination_dot) = ratline.ref_(autorouter).endpoint_dots(); - let origin_layer = autorouter - .board() - .layout() - .drawing() - .primitive(origin_dot) - .layer(); - let destination_layer = autorouter - .board() - .layout() - .drawing() - .primitive(destination_dot) - .layer(); - - if let (Some(origin_layername), Some(destination_layername)) = ( - autorouter + let origin_net = autorouter .board() .layout() - .rules() - .layer_layername(origin_layer), - autorouter + .drawing() + .primitive(origin_dot) + .maybe_net(); + let destination_net = autorouter .board() .layout() - .rules() - .layer_layername(destination_layer), - ) { - assert_eq!(origin_layername, destination_layername); + .drawing() + .primitive(destination_dot) + .maybe_net(); + assert_eq!(origin_net, destination_net); - if origin_layername != layername { - continue; - } - } else { - assert!(false); - } + let net = origin_net.unwrap(); - let origin_net = autorouter - .board() - .layout() - .drawing() - .primitive(origin_dot) - .maybe_net(); - let destination_net = autorouter - .board() - .layout() - .drawing() - .primitive(destination_dot) - .maybe_net(); - assert_eq!(origin_net, destination_net); + if let Some(netname) = autorouter.board().layout().rules().net_netname(net) { + // We don't route ground. + let origin = conncomps.unionfind().find(origin_dot.index()); + let destination = conncomps.unionfind().find(destination_dot.index()); - let net = origin_net.unwrap(); - - if let Some(netname) = autorouter.board().layout().rules().net_netname(net) { - // We don't route ground. - let org = conncomps.unionfind().find(origin_dot.index()); - let desc = conncomps.unionfind().find(destination_dot.index()); - - if netname != "GND" { - assert_eq!(org, desc); + if netname != "GND" { + assert_eq!(origin, destination); + } } } } diff --git a/tests/multilayer.rs b/tests/multilayer.rs index 7d9b8c2..33e9490 100644 --- a/tests/multilayer.rs +++ b/tests/multilayer.rs @@ -34,6 +34,22 @@ fn test_unrouted_lm317_breakout() { )); } +#[test] +fn test_astable_multivibrator() { + let mut invoker = common::create_invoker_and_assert(common::load_design( + "tests/multilayer/astable_multivibrator/astable_multivibrator.dsn", + )); + + common::replay_and_assert_and_report( + &mut invoker, + "tests/multilayer/astable_multivibrator/autoroute_all.cmd", + "plain", + ); + + let (mut autorouter, ..) = invoker.dissolve(); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); +} + #[test] fn test_signal_integrity_test() { let invoker = common::create_invoker_and_assert(common::load_design( diff --git a/tests/multilayer/astable_multivibrator/autoroute_all.cmd b/tests/multilayer/astable_multivibrator/autoroute_all.cmd new file mode 100644 index 0000000..ca5d6ee --- /dev/null +++ b/tests/multilayer/astable_multivibrator/autoroute_all.cmd @@ -0,0 +1,130 @@ +{ + "done": [ + { + "MultilayerAutoroute": [ + [ + { + "pin": "C1-1", + "layer": "B.Cu" + }, + { + "pin": "C1-2", + "layer": "B.Cu" + }, + { + "pin": "C2-1", + "layer": "B.Cu" + }, + { + "pin": "C2-2", + "layer": "B.Cu" + }, + { + "pin": "D1-1", + "layer": "F.Cu" + }, + { + "pin": "D1-2", + "layer": "F.Cu" + }, + { + "pin": "D2-1", + "layer": "F.Cu" + }, + { + "pin": "D2-2", + "layer": "F.Cu" + }, + { + "pin": "J1-1", + "layer": "B.Cu" + }, + { + "pin": "J1-1", + "layer": "F.Cu" + }, + { + "pin": "J2-1", + "layer": "B.Cu" + }, + { + "pin": "J2-1", + "layer": "F.Cu" + }, + { + "pin": "Q1-1", + "layer": "B.Cu" + }, + { + "pin": "Q1-2", + "layer": "B.Cu" + }, + { + "pin": "Q1-3", + "layer": "B.Cu" + }, + { + "pin": "Q2-1", + "layer": "B.Cu" + }, + { + "pin": "Q2-2", + "layer": "B.Cu" + }, + { + "pin": "Q2-3", + "layer": "B.Cu" + }, + { + "pin": "R1-1", + "layer": "B.Cu" + }, + { + "pin": "R1-2", + "layer": "B.Cu" + }, + { + "pin": "R2-1", + "layer": "B.Cu" + }, + { + "pin": "R2-2", + "layer": "B.Cu" + }, + { + "pin": "R3-1", + "layer": "B.Cu" + }, + { + "pin": "R3-2", + "layer": "B.Cu" + }, + { + "pin": "R4-1", + "layer": "B.Cu" + }, + { + "pin": "R4-2", + "layer": "B.Cu" + } + ], + { + "anterouter": { + "fanout_clearance": 200.0 + }, + "planar": { + "principal_layer": 0, + "presort_by": "RatlineIntersectionCountAndLength", + "permutate": true, + "router": { + "routed_band_width": 100.0, + "wrap_around_bands": true, + "squeeze_through_under_bends": true + } + } + } + ] + } + ], + "undone": [] +} \ No newline at end of file diff --git a/tests/single_layer.rs b/tests/single_layer.rs index 2673a95..3f97982 100644 --- a/tests/single_layer.rs +++ b/tests/single_layer.rs @@ -35,10 +35,7 @@ fn autoroute_4x4_1206_led_matrix_breakout(variant: &str) { ); let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); } #[apply(test_master)] @@ -54,10 +51,7 @@ fn autoroute_4x4_1206_led_matrix_breakout_in_predefined_order(variant: &str) { ); let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); } #[apply(test_master)] @@ -73,10 +67,7 @@ fn autoroute_tht_de9_to_tht_de9(variant: &str) { ); let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); } #[apply(test_master)] @@ -91,10 +82,7 @@ fn autoroute_tht_de9_to_tht_de9_in_predefined_order(variant: &str) { ); let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); } #[apply(test_master)] @@ -110,10 +98,7 @@ fn autoroute_0603_breakout(variant: &str) { let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); //common::assert_number_of_conncomps(&mut autorouter, 2); } @@ -132,10 +117,7 @@ fn autoroute_tht_diode_bridge_rectifier(variant: &str) { let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); //common::assert_number_of_conncomps(&mut autorouter, 4); common::assert_band_length(autorouter.board(), "J2-2", "D4-2", 15906.760439007436, 0.01); @@ -172,10 +154,7 @@ fn autoroute_4x_3rd_order_smd_lc_filters(variant: &str) { let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); //common::assert_number_of_conncomps(&mut autorouter, 16); } @@ -216,10 +195,7 @@ fn autoroute_vga_dac_breakout(variant: &str) { let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); } #[apply(test_master)] @@ -237,9 +213,6 @@ fn autoroute_smd_non_rectangular_buck_converter(variant: &str) { let (mut autorouter, ..) = invoker.dissolve(); - common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( - &mut autorouter, - "F.Cu", - ); + common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter); //common::assert_number_of_conncomps(&mut autorouter, 16); }