// SPDX-FileCopyrightText: 2024 Topola contributors // // SPDX-License-Identifier: MIT //! Module for handling Specctra's mesadata - design rules, as well as layers //! or net properties use bimap::BiBTreeMap; use std::collections::BTreeMap; use crate::{ rules::{AccessRules, Conditions}, structure::Pcb, }; /// Trait for managing the Specctra's mesadata /// /// This trait implements generic function for accessing or modifying different /// compounds of board parts like nets or layers pub trait AccessMesadata: AccessRules { /// Renames a layer based on its index. fn bename_layer(&mut self, layer: usize, layername: String); /// Retrieves the name of a layer by its index. fn layer_layername(&self, layer: usize) -> Option<&str>; /// Retrieves the index of a layer by its name. fn layername_layer(&self, layername: &str) -> Option; /// Renames a net based on its index. fn bename_net(&mut self, net: usize, netname: String); /// Retrieves the name of a net by its index. fn net_netname(&self, net: usize) -> Option<&str>; /// Retrieves the index of a net by its name. fn netname_net(&self, netname: &str) -> Option; } #[derive(Clone, Debug)] /// [`SpecctraRule`] represents the basic routing constraints used by an auto-router, such as /// the Topola auto-router, in a PCB design process. This struct defines two key design /// rules: the width of the trace and the minimum clearance between electrical features. pub struct SpecctraRule { /// Specifies the width of the trace (or conductor) in millimeters. /// This value ensures that the traces meet electrical /// and mechanical requirements, such as current-carrying capacity or signal integrity. pub width: f64, /// Defines the minimum clearance (spacing) between traces, pads, /// or other conductive features on the PCB. Adequate clearance is important for /// preventing electrical shorts or interference between signals, and is often /// dictated by manufacturing constraints or voltage considerations. pub clearance: f64, } impl SpecctraRule { fn from_dsn(rule: &super::structure::Rule) -> Self { Self { width: rule.width as f64, clearance: rule.clearances[0].value as f64, // picks the generic clearance only for now } } } #[derive(Clone, Debug)] /// [`SpecctraMesadata`] holds the metadata required by the Specctra auto-router to /// understand and enforce design rules across various net classes and layers in a PCB layout. /// This struct encapsulates information about rules for individual nets, net classes, /// layers, and their corresponding relationships. pub struct SpecctraMesadata { /// The default routing rule applied globally if no specific net class rule is defined. structure_rule: SpecctraRule, // net class name -> rule /// A map from net class names to their specific `SpecctraRule` constraints. /// These rules are applied to all nets belonging to the respective net clas class_rules: BTreeMap, // layername <-> layer for Layout /// A bidirectional map between layer indices and layer names, allowing translation /// between index-based layers in the layout and user-defined layer names. pub layer_layername: BiBTreeMap, // netname <-> net for Layout /// A bidirectional map between network indices and network names in the PCB layout, /// providing an easy way to reference nets by name or index. pub net_netname: BiBTreeMap, // net -> netclass /// A map that associates network indices with their respective net class names. /// This is used to apply net class-specific routing rules to each net. net_netclass: BTreeMap, } impl SpecctraMesadata { /// Creates a [`SpecctraMesadata`] instance from a given `Pcb` reference. /// /// This function extracts the necessary metadata from the `Pcb` struct, such as /// layer-to-layer name mappings, net-to-net name mappings, and net class rules. pub fn from_pcb(pcb: &Pcb) -> Self { let layer_layername = BiBTreeMap::from_iter( pcb.structure .layers .iter() .enumerate() .map(|(index, layer)| (index, layer.name.clone())), ); // assign IDs to all nets named in pcb.network let net_netname = { let mut tmp: Vec<_> = pcb .network .classes .iter() .flat_map(|class| &class.nets) .chain(pcb.network.nets.iter().map(|net| &net.name)) .collect(); // deduplicate net names tmp.sort_unstable(); tmp.dedup(); BiBTreeMap::from_iter(tmp.into_iter().cloned().enumerate()) }; let mut net_netclass = BTreeMap::new(); let class_rules = BTreeMap::from_iter( pcb.network .classes .iter() .inspect(|class| { for netname in &class.nets { // this can't panic because we did iterate over // `pcb.network.classes[].nets` above let net = net_netname.get_by_right(netname).unwrap(); net_netclass.insert(*net, class.name.clone()); } }) .map(|class| (class.name.clone(), SpecctraRule::from_dsn(&class.rule))), ); let mut structure_rule = super::structure::Rule { width: 0.0, clearances: Vec::new(), }; // workaround for differing syntax // collapse multiple rule entries into a single one for rule in &pcb.structure.rules { if let Some(width) = rule.width { structure_rule.width = width } structure_rule .clearances .extend_from_slice(&rule.clearances); } Self { structure_rule: SpecctraRule::from_dsn(&structure_rule), class_rules, layer_layername, net_netname, net_netclass, } } /// Retrieves the Specctra routing rule associated with a specified net ID. /// /// This function looks up the routing rule for a given net ID. It first checks if the net is /// associated with a net class. If a net class is found, it retrieves the corresponding rule /// from the class rules. If no class is associated, or if the class does not have a defined rule, /// it defaults to the general structure rule. /// pub fn get_rule(&self, net: usize) -> &SpecctraRule { self.net_netclass .get(&net) .and_then(|netclass| self.class_rules.get(netclass)) .unwrap_or(&self.structure_rule) } } impl AccessRules for SpecctraMesadata { fn clearance(&self, conditions1: &Conditions<'_>, conditions2: &Conditions<'_>) -> f64 { let clr1 = self.get_rule(conditions1.net).clearance; let clr2 = self.get_rule(conditions2.net).clearance; f64::max(clr1, clr2) } fn largest_clearance(&self, _maybe_net: Option) -> f64 { self.class_rules .values() .map(|rule| rule.clearance) .reduce(f64::max) .unwrap_or(0.0) } } impl AccessMesadata for SpecctraMesadata { fn bename_layer(&mut self, layer: usize, layername: String) { self.layer_layername.insert(layer, layername); } fn layer_layername(&self, layer: usize) -> Option<&str> { self.layer_layername.get_by_left(&layer).map(|s| s.as_str()) } fn layername_layer(&self, layername: &str) -> Option { self.layer_layername.get_by_right(layername).copied() } fn bename_net(&mut self, net: usize, netname: String) { self.net_netname.insert(net, netname); } fn net_netname(&self, net: usize) -> Option<&str> { self.net_netname.get_by_left(&net).map(|s| s.as_str()) } fn netname_net(&self, netname: &str) -> Option { self.net_netname.get_by_right(netname).copied() } }