// SPDX-FileCopyrightText: 2024 Topola contributors // // SPDX-License-Identifier: MIT use std::{fs::File, io::BufReader}; use topola::{ autorouter::{ conncomps::ConncompsWithPrincipalLayer, history::{History, HistoryError}, invoker::{Invoker, InvokerError}, Autorouter, }, board::{edit::BoardEdit, AccessMesadata, Board}, drawing::{ graph::{GetMaybeNet, MakePrimitiveRef, PrimitiveIndex}, primitive::MakePrimitiveShape, }, geometry::{shape::MeasureLength, GenericNode, GetLayer}, graph::{GetIndex, MakeRef}, router::{navmesh::Navmesh, RouterOptions}, specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata}, }; pub fn load_design(filename: &str) -> Autorouter { let design_file = File::open(filename).unwrap(); let design_bufread = BufReader::new(design_file); let design = SpecctraDesign::load(design_bufread).unwrap(); Autorouter::new(design.make_board(&mut BoardEdit::new())).unwrap() } pub fn create_invoker_and_assert( autorouter: Autorouter, ) -> Invoker { let mut invoker = Invoker::new(autorouter); assert!(matches!( invoker.undo(), Err(InvokerError::History(HistoryError::NoPreviousCommand)) )); assert!(matches!( invoker.redo(), Err(InvokerError::History(HistoryError::NoNextCommand)) )); invoker } pub fn replay_and_assert_and_report( invoker: &mut Invoker, filename: &str, variant: &str, ) { let file = File::open(filename).unwrap(); let history: History = serde_json::from_reader(file).unwrap(); invoker.replay(history); report_route_lengths(invoker.autorouter()); if variant == "with_undo_redo_replay" { assert_undo_redo_replay(invoker, filename); } } pub fn assert_undo_redo_replay(invoker: &mut Invoker, filename: &str) { let prev_node_count = invoker.autorouter().board().layout().drawing().node_count(); // Sanity test: check if node count remained the same after some attempts at undo-redo. if invoker.redo().is_ok() { let _ = invoker.undo(); } if invoker.undo().is_ok() { if invoker.undo().is_ok() { let _ = invoker.redo(); } let _ = invoker.redo(); } assert_eq!( invoker.autorouter().board().layout().drawing().node_count(), prev_node_count, ); // Another sanity test: undo all, and then replay again. This protects // against undo failing to remove something that does not cause the // subsequent redo to fail or something that the redo restores idempotently. let file = File::open(filename).unwrap(); let history: History = serde_json::from_reader(file).unwrap(); undo_all_and_assert(invoker); invoker.replay(history); assert_eq!( invoker.autorouter().board().layout().drawing().node_count(), prev_node_count, ); } pub fn undo_all_and_assert(invoker: &mut Invoker) { for _ in 0..invoker.history().done().len() { invoker.undo().unwrap(); } assert!(matches!( invoker.undo(), Err(InvokerError::History(HistoryError::NoPreviousCommand)) )); assert_no_loose_nodes(invoker.autorouter()); } pub fn assert_no_loose_nodes(autorouter: &Autorouter) { for node in autorouter.board().layout().drawing().primitive_nodes() { match node { PrimitiveIndex::LooseDot(..) | PrimitiveIndex::LoneLooseSeg(..) | PrimitiveIndex::SeqLooseSeg(..) | PrimitiveIndex::LooseBend(..) => assert!(false), _ => (), } } } pub fn assert_navnode_count( autorouter: &mut Autorouter, origin_pin: &str, destination_pin: &str, expected_count: usize, ) { let (origin, destination) = autorouter .ratsnests() .on_principal_layer(0) .graph() .edge_indices() .collect::>() .iter() .find_map(|ratline| { let (candidate_origin, candidate_destination) = ratline.ref_(autorouter).terminating_dots(); let candidate_origin_pin = autorouter .board() .node_pinname(&GenericNode::Primitive(candidate_origin.into())) .unwrap(); let candidate_destination_pin = autorouter .board() .node_pinname(&GenericNode::Primitive(candidate_destination.into())) .unwrap(); ((candidate_origin_pin == origin_pin && candidate_destination_pin == destination_pin) || (candidate_origin_pin == destination_pin && candidate_destination_pin == origin_pin)) .then_some((candidate_origin, candidate_destination)) }) .unwrap(); let navmesh = Navmesh::new( autorouter.board().layout(), origin, destination, RouterOptions { wrap_around_bands: true, squeeze_through_under_bends: false, routed_band_width: 100.0, }, ) .unwrap(); assert_eq!(navmesh.graph().node_count(), expected_count); } pub fn assert_that_all_single_layer_groundless_ratlines_are_autorouted( autorouter: &mut Autorouter, layername: &str, ) { let conncomps = ConncompsWithPrincipalLayer::new(autorouter.board(), 0); for ratline in autorouter .ratsnests() .on_principal_layer(0) .graph() .edge_indices() { 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() .drawing() .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 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); } } } } /*pub fn assert_number_of_conncomps( autorouter: &mut Autorouter, conncomp_count: usize, ) { let unionfind = unionfind(autorouter); let mut labels = unionfind.into_labeling(); labels.sort_unstable(); labels.dedup(); assert_eq!(labels.len(), conncomp_count); }*/ pub fn assert_band_length( board: &Board, source_pin: &str, target_pin: &str, expected_length: f64, rel_err: f64, ) { let band = board.band_between_pins(source_pin, target_pin).unwrap(); let band_length = band[false].ref_(board.layout().drawing()).length(); assert!( (band_length - expected_length).abs() < expected_length * rel_err, "band_length = {}, expected_length = {}, epsilon = {}", band_length, expected_length, rel_err ); } pub fn report_route_lengths(autorouter: &Autorouter) { let mut total_length = 0.0; for layer in 0..autorouter.board().layout().drawing().layer_count() { let mut layer_total_length = 0.0; for primitive in autorouter .board() .layout() .drawing() .layer_primitive_nodes(layer) { match primitive { PrimitiveIndex::LooseDot(..) | PrimitiveIndex::LoneLooseSeg(..) | PrimitiveIndex::SeqLooseSeg(..) => { layer_total_length += primitive .primitive_ref(autorouter.board().layout().drawing()) .shape() .length(); } _ => (), } } dbg!(layer, layer_total_length); total_length += layer_total_length; } dbg!(total_length); }