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-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<usize>,
}
impl ConncompsWithPrincipalLayer {
pub fn new(board: &Board<impl AccessMesadata>, principal_layer: usize) -> Self {
impl Conncomps {
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 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<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(
board: &Board<impl AccessMesadata>,
unionfind: &mut UnionFind<usize>,

View File

@ -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,

View File

@ -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<impl AccessMesadata>,
principal_layer: usize,
) -> Result<Self, InsertionError> {
let conncomps = ConncompsWithPrincipalLayer::new(board, principal_layer);
let conncomps = Conncomps::new_with_principal_layer(board, principal_layer);
let mut this = Self {
graph: StableUnGraph::default(),

View File

@ -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,64 +176,25 @@ 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<impl AccessMesadata>,
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 principal_layer in 0..autorouter.board().layout().drawing().layer_count() {
for ratline in autorouter
.ratsnests()
.on_principal_layer(layer)
.on_principal_layer(principal_layer)
.graph()
.edge_indices()
.map(|index| RatlineUid {
principal_layer: layer,
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
.board()
.layout()
.rules()
.layer_layername(origin_layer),
autorouter
.board()
.layout()
.rules()
.layer_layername(destination_layer),
) {
assert_eq!(origin_layername, destination_layername);
if origin_layername != layername {
continue;
}
} else {
assert!(false);
}
let origin_net = autorouter
.board()
.layout()
@ -252,11 +213,12 @@ pub fn assert_that_all_single_layer_groundless_ratlines_are_autorouted(
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());
let origin = conncomps.unionfind().find(origin_dot.index());
let destination = conncomps.unionfind().find(destination_dot.index());
if netname != "GND" {
assert_eq!(org, desc);
assert_eq!(origin, destination);
}
}
}
}

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]
fn test_signal_integrity_test() {
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();
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);
}