test: Add stable multivibrator test, the first multilayer autorouting test

This commit is contained in:
Mikolaj Wielgus 2025-10-20 00:30:44 +02:00
parent dd8a4c5808
commit 63764a7e29
7 changed files with 239 additions and 132 deletions

View File

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2025 Topola contributors // SPDX-FileCopyrightText: 2025 Topola contributors
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::collections::BTreeSet; use std::collections::BTreeSet;
use derive_getters::Getters; use derive_getters::Getters;
@ -16,12 +15,32 @@ use crate::{
}; };
#[derive(Clone, Getters)] #[derive(Clone, Getters)]
pub struct ConncompsWithPrincipalLayer { pub struct Conncomps {
unionfind: UnionFind<usize>, unionfind: UnionFind<usize>,
} }
impl ConncompsWithPrincipalLayer { impl Conncomps {
pub fn new(board: &Board<impl AccessMesadata>, principal_layer: usize) -> Self { pub fn new(board: &Board<impl AccessMesadata>) -> 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<impl AccessMesadata>,
principal_layer: usize,
) -> Self {
let mut principally_visited_pins = BTreeSet::new(); let mut principally_visited_pins = BTreeSet::new();
let mut unionfind = UnionFind::new(board.layout().drawing().geometry().dot_index_bound()); let mut unionfind = UnionFind::new(board.layout().drawing().geometry().dot_index_bound());
@ -42,23 +61,7 @@ impl ConncompsWithPrincipalLayer {
// their primitives. // their primitives.
for pinname in board.pinnames() { for pinname in board.pinnames() {
if principally_visited_pins.contains(pinname) { if principally_visited_pins.contains(pinname) {
let mut iter = board.pinname_nodes(pinname); Self::unionize_pin(board, &mut unionfind, 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);
}
}
} }
} }
@ -99,6 +102,29 @@ impl ConncompsWithPrincipalLayer {
} }
} }
fn unionize_pin(
board: &Board<impl AccessMesadata>,
unionfind: &mut UnionFind<usize>,
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( fn unionize_to_common(
board: &Board<impl AccessMesadata>, board: &Board<impl AccessMesadata>,
unionfind: &mut UnionFind<usize>, unionfind: &mut UnionFind<usize>,

View File

@ -7,6 +7,7 @@ use petgraph::graph::{EdgeIndex, NodeIndex};
use specctra_core::mesadata::AccessMesadata; use specctra_core::mesadata::AccessMesadata;
use crate::{ use crate::{
autorouter::{ratsnest::RatvertexNodeIndex, Autorouter},
drawing::{ drawing::{
band::BandTermsegIndex, band::BandTermsegIndex,
dot::FixedDotIndex, dot::FixedDotIndex,
@ -17,8 +18,6 @@ use crate::{
triangulation::GetTrianvertexNodeIndex, triangulation::GetTrianvertexNodeIndex,
}; };
use super::{ratsnest::RatvertexNodeIndex, Autorouter};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct RatlineUid { pub struct RatlineUid {
pub principal_layer: usize, pub principal_layer: usize,

View File

@ -18,6 +18,7 @@ use spade::{handles::FixedVertexHandle, HasPosition, InsertionError, Point2};
use specctra_core::mesadata::AccessMesadata; use specctra_core::mesadata::AccessMesadata;
use crate::{ use crate::{
autorouter::conncomps::Conncomps,
board::Board, board::Board,
drawing::{ drawing::{
band::BandTermsegIndex, band::BandTermsegIndex,
@ -31,7 +32,7 @@ use crate::{
triangulation::{GetTrianvertexNodeIndex, Triangulation}, triangulation::{GetTrianvertexNodeIndex, Triangulation},
}; };
use super::{conncomps::ConncompsWithPrincipalLayer, ratline::RatlineWeight}; use super::ratline::RatlineWeight;
#[enum_dispatch(GetIndex)] #[enum_dispatch(GetIndex)]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
@ -113,7 +114,7 @@ impl Ratsnest {
board: &Board<impl AccessMesadata>, board: &Board<impl AccessMesadata>,
principal_layer: usize, principal_layer: usize,
) -> Result<Self, InsertionError> { ) -> Result<Self, InsertionError> {
let conncomps = ConncompsWithPrincipalLayer::new(board, principal_layer); let conncomps = Conncomps::new_with_principal_layer(board, principal_layer);
let mut this = Self { let mut this = Self {
graph: StableUnGraph::default(), graph: StableUnGraph::default(),

View File

@ -6,7 +6,7 @@ use std::{fs::File, io::BufReader};
use topola::{ use topola::{
autorouter::{ autorouter::{
conncomps::ConncompsWithPrincipalLayer, conncomps::Conncomps,
history::{History, HistoryError}, history::{History, HistoryError},
invoker::{Invoker, InvokerError}, invoker::{Invoker, InvokerError},
ratline::RatlineUid, ratline::RatlineUid,
@ -176,87 +176,49 @@ pub fn assert_layer_0_navnode_count(
assert_eq!(navmesh.graph().node_count(), expected_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<impl AccessMesadata>, autorouter: &mut Autorouter<impl AccessMesadata>,
layername: &str,
) { ) {
let layer = autorouter let conncomps = Conncomps::new(autorouter.board());
.board() assert!(autorouter.board().layout().drawing().layer_count() >= 1);
.layout()
.rules()
.layername_layer(layername)
.unwrap();
let conncomps = ConncompsWithPrincipalLayer::new(autorouter.board(), layer);
for ratline in autorouter for principal_layer in 0..autorouter.board().layout().drawing().layer_count() {
.ratsnests() for ratline in autorouter
.on_principal_layer(layer) .ratsnests()
.graph() .on_principal_layer(principal_layer)
.edge_indices() .graph()
.map(|index| RatlineUid { .edge_indices()
principal_layer: layer, .map(|index| RatlineUid {
index, principal_layer,
}) index,
{ })
let (origin_dot, destination_dot) = ratline.ref_(autorouter).endpoint_dots(); {
let (origin_dot, destination_dot) = ratline.ref_(autorouter).endpoint_dots();
let origin_layer = autorouter let origin_net = 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
.board() .board()
.layout() .layout()
.rules() .drawing()
.layer_layername(origin_layer), .primitive(origin_dot)
autorouter .maybe_net();
let destination_net = autorouter
.board() .board()
.layout() .layout()
.rules() .drawing()
.layer_layername(destination_layer), .primitive(destination_dot)
) { .maybe_net();
assert_eq!(origin_layername, destination_layername); assert_eq!(origin_net, destination_net);
if origin_layername != layername { let net = origin_net.unwrap();
continue;
}
} else {
assert!(false);
}
let origin_net = autorouter if let Some(netname) = autorouter.board().layout().rules().net_netname(net) {
.board() // We don't route ground.
.layout() let origin = conncomps.unionfind().find(origin_dot.index());
.drawing() let destination = conncomps.unionfind().find(destination_dot.index());
.primitive(origin_dot)
.maybe_net();
let destination_net = autorouter
.board()
.layout()
.drawing()
.primitive(destination_dot)
.maybe_net();
assert_eq!(origin_net, destination_net);
let net = origin_net.unwrap(); if netname != "GND" {
assert_eq!(origin, destination);
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);
} }
} }
} }

View File

@ -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] #[test]
fn test_signal_integrity_test() { fn test_signal_integrity_test() {
let invoker = common::create_invoker_and_assert(common::load_design( let invoker = common::create_invoker_and_assert(common::load_design(

View File

@ -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": []
}

View File

@ -35,10 +35,7 @@ fn autoroute_4x4_1206_led_matrix_breakout(variant: &str) {
); );
let (mut autorouter, ..) = invoker.dissolve(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
} }
#[apply(test_master)] #[apply(test_master)]
@ -54,10 +51,7 @@ fn autoroute_4x4_1206_led_matrix_breakout_in_predefined_order(variant: &str) {
); );
let (mut autorouter, ..) = invoker.dissolve(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
} }
#[apply(test_master)] #[apply(test_master)]
@ -73,10 +67,7 @@ fn autoroute_tht_de9_to_tht_de9(variant: &str) {
); );
let (mut autorouter, ..) = invoker.dissolve(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
} }
#[apply(test_master)] #[apply(test_master)]
@ -91,10 +82,7 @@ fn autoroute_tht_de9_to_tht_de9_in_predefined_order(variant: &str) {
); );
let (mut autorouter, ..) = invoker.dissolve(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
} }
#[apply(test_master)] #[apply(test_master)]
@ -110,10 +98,7 @@ fn autoroute_0603_breakout(variant: &str) {
let (mut autorouter, ..) = invoker.dissolve(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
//common::assert_number_of_conncomps(&mut autorouter, 2); //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(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
//common::assert_number_of_conncomps(&mut autorouter, 4); //common::assert_number_of_conncomps(&mut autorouter, 4);
common::assert_band_length(autorouter.board(), "J2-2", "D4-2", 15906.760439007436, 0.01); 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(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
//common::assert_number_of_conncomps(&mut autorouter, 16); //common::assert_number_of_conncomps(&mut autorouter, 16);
} }
@ -216,10 +195,7 @@ fn autoroute_vga_dac_breakout(variant: &str) {
let (mut autorouter, ..) = invoker.dissolve(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
} }
#[apply(test_master)] #[apply(test_master)]
@ -237,9 +213,6 @@ fn autoroute_smd_non_rectangular_buck_converter(variant: &str) {
let (mut autorouter, ..) = invoker.dissolve(); let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_single_layer_groundless_ratlines_are_autorouted( common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
&mut autorouter,
"F.Cu",
);
//common::assert_number_of_conncomps(&mut autorouter, 16); //common::assert_number_of_conncomps(&mut autorouter, 16);
} }