diff --git a/Cargo.lock b/Cargo.lock index 66690bc6..6c39fe41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,12 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "aph_disjoint_set" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec916e7c5883e65b44aead1b3b77c144e518686472524896868cb00366937c08" + [[package]] name = "approx" version = "0.5.1" @@ -589,7 +595,7 @@ dependencies = [ "log", "serde", "toml", - "toml_edit 0.22.24", + "toml_edit 0.22.27", "windows-sys 0.59.0", ] @@ -1438,6 +1444,7 @@ dependencies = [ name = "layir" version = "0.2.1" dependencies = [ + "aph_disjoint_set", "arcstr", "enumify", "geometry", @@ -2022,7 +2029,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.24", + "toml_edit 0.22.27", ] [[package]] @@ -2492,9 +2499,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -3058,14 +3065,14 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.24", + "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] @@ -3083,17 +3090,24 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.6", + "toml_write", + "winnow 0.7.11", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tonic" version = "0.13.0" @@ -3700,9 +3714,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] diff --git a/libs/layir/Cargo.toml b/libs/layir/Cargo.toml index 7297afb9..764b1ad3 100644 --- a/libs/layir/Cargo.toml +++ b/libs/layir/Cargo.toml @@ -13,4 +13,5 @@ thiserror = "2" enumify = { version = "0.2.1", registry = "substrate", path = "../enumify" } uniquify = { version = "0.4.0", registry = "substrate", path = "../uniquify" } geometry = { version = "0.7.1", registry = "substrate", path = "../geometry" } +aph_disjoint_set = "0.1.0" diff --git a/libs/layir/src/connectivity.rs b/libs/layir/src/connectivity.rs new file mode 100644 index 00000000..5a0191a1 --- /dev/null +++ b/libs/layir/src/connectivity.rs @@ -0,0 +1,244 @@ +use crate::CellId; +use crate::Instance; +use aph_disjoint_set::DisjointSet; +use geometry::bbox::Bbox; +use geometry::rect::Rect; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; + +use crate::Element; +use crate::{Cell, Library, Shape, TransformRef, Transformation}; + +/// Returns true if two shapes overlap on a flat plane, and false otherwise. +fn intersect_shapes(shape1: &Shape, shape2: &Shape) -> bool { + let shape1_bbox: Rect = shape1.bbox_rect(); + let shape2_bbox: Rect = shape2.bbox_rect(); + shape1_bbox.intersection(shape2_bbox).is_some() +} + +/// Returns a vector of all shapes from a given cell instance and its children +/// with their coordinates transformed into the coordinate system of the instance's parent. +fn flatten_instance(inst: &Instance, lib: &Library) -> Vec> +where + L: Connectivity + Clone, +{ + let mut ret: Vec> = vec![]; + + let cellid: CellId = inst.child(); + let transform: Transformation = inst.transformation(); + + // Add all shape elements directly from child cell after applying the instances transformation + for elem in lib.cell(cellid).elements() { + if let Element::Shape(shape) = elem { + let transformed_shape = shape.transform_ref(transform); + ret.push(transformed_shape); + } + } + + // Recursively flatten child instances and apply cumulative transformations + for instance in lib.cell(cellid).instances() { + let mut flattened_shapes = flatten_instance::(instance.1, lib); + for flattened_shape in &mut flattened_shapes { + *flattened_shape = flattened_shape.transform_ref(transform); + } + ret.append(&mut flattened_shapes); + } + + ret +} + +/// Returns a vector of all shapes from a single cell's transformed child instances and itself. +fn flatten_cell(cell: &Cell, lib: &Library) -> Vec> +where + L: Connectivity + Clone, +{ + let mut ret: Vec> = vec![]; + + // No transformation needed + for elem in cell.elements() { + if let Element::Shape(shape) = elem { + ret.push(shape.clone()); + } + } + + for inst in cell.instances() { + for flattened_shape in flatten_instance::(inst.1, lib) { + ret.push(flattened_shape); + } + } + + ret +} + +pub trait Connectivity: Sized + PartialEq + Eq + Clone + Hash { + fn connected_layers(&self) -> Vec; + + /// Returns a vector of layers connected to a given layer. + fn connected(&self, other: &Self) -> bool { + self.connected_layers().contains(other) + } + + /// Returns a vector of unique hashsets of all connected groups of connected child shapes in a given cell. + fn connected_components(cell: &Cell, lib: &Library) -> Vec>> + where + Self: Clone, + { + // All sub-shapes contained in given cell + let all_shapes = flatten_cell::(cell, lib); + let mut djs = DisjointSet::new(all_shapes.len()); + + // Build disjoint sets based on overlap and layer connectivity + for (start_index, start_shape) in all_shapes.clone().into_iter().enumerate() { + for (other_index, other_shape) in all_shapes.clone().into_iter().enumerate() { + if start_index != other_index + && intersect_shapes::(&start_shape, &other_shape) + && start_shape.layer().connected(other_shape.layer()) + { + djs.union(start_index, other_index); + } + } + } + + let mut component_map: HashMap>> = HashMap::new(); + + for (start_index, start_shape) in all_shapes.into_iter().enumerate() { + let root: usize = djs.get_root(start_index).into_inner(); + component_map.entry(root).or_default().insert(start_shape); + } + + component_map.into_values().collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Cell, Instance, LibraryBuilder, Shape}; + use geometry::rect::Rect; + use std::collections::{HashMap, HashSet}; + + // This struct helps check if two shapes are connected after connected_components has been run + struct ComponentLookup { + shape_to_component_id: HashMap, usize>, + } + + impl ComponentLookup + where + L: Connectivity + Clone, + { + /// Creates a new ComponentLookup from a vector of connected components. + fn new(components: Vec>>) -> Self { + let mut shape_to_component_id = HashMap::new(); + for (component_id, component_set) in components.into_iter().enumerate() { + for shape in component_set.into_iter() { + shape_to_component_id.insert(shape, component_id); + } + } + ComponentLookup { + shape_to_component_id, + } + } + + /// Returns true if both shapes are found and belong to the same component, and false otherwise. + fn are_connected(&self, s1: &Shape, s2: &Shape) -> bool { + let comp_id1 = self.shape_to_component_id.get(s1); + let comp_id2 = self.shape_to_component_id.get(s2); + + match (comp_id1, comp_id2) { + // If both shapes are found, check if their component IDs are the same + (Some(&id1), Some(&id2)) => id1 == id2, + _ => false, + } + } + } + + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + enum Layer { + Met1, + Via1, + Met2, + Outside, + } + + impl Connectivity for Layer { + fn connected_layers(&self) -> Vec { + match self { + Self::Met1 => vec![Self::Met1, Self::Via1], + Self::Via1 => vec![Self::Met1, Self::Via1, Self::Met2], + Self::Met2 => vec![Self::Via1, Self::Met2], + Self::Outside => vec![], + } + } + } + + #[test] + fn test_complete() { + let mut small_cell: Cell = Cell::new("small cell test"); + let mut big_cell: Cell = Cell::new("big cell test"); + let mut lib_builder: LibraryBuilder = LibraryBuilder::new(); + + // Build small cell first and add to big cell + let m2_shape1 = Shape::new(Layer::Met2, Rect::from_sides(0, 0, 30, 30)); + small_cell.add_element(m2_shape1.clone()); + + lib_builder.add_cell(small_cell.clone()); + let small_cell_id = lib_builder.cells().next().unwrap().0; + + let small_cell_instance = Instance::new(small_cell_id, "small_cell_test"); + big_cell.add_instance(small_cell_instance); + + // Build big cell + let m1_shape = Shape::new(Layer::Met1, Rect::from_sides(0, 0, 100, 100)); + let v1_shape = Shape::new(Layer::Via1, Rect::from_sides(10, 10, 100, 100)); + let m2_shape2 = Shape::new(Layer::Met2, Rect::from_sides(10, 10, 50, 50)); + let m2_shape3 = Shape::new(Layer::Met2, Rect::from_sides(1000, 1000, 5000, 5000)); + big_cell.add_element(m1_shape.clone()); + big_cell.add_element(v1_shape.clone()); + big_cell.add_element(m2_shape2.clone()); + big_cell.add_element(m2_shape3.clone()); + + lib_builder.add_cell(big_cell.clone()); + + // Build fixed library + let lib = lib_builder.clone().build().unwrap(); + + // Add an outside shape for testing + let outside_shape = Shape::new(Layer::Outside, Rect::from_sides(0, 0, 100, 100)); + + // Find all connected components of big_cell's child shapes + let components_vec = Layer::connected_components(&big_cell, &lib); + + let component_lookup = ComponentLookup::new(components_vec.clone()); + + // Expected connections + assert!( + component_lookup.are_connected(&m1_shape, &v1_shape), + "m1_shape should be connected to v1_shape" + ); + assert!( + component_lookup.are_connected(&m1_shape, &m2_shape2), + "m1_shape should be connected to m2_shape2" + ); + assert!( + component_lookup.are_connected(&m1_shape, &m2_shape1), // m2_shape1 is from the instance + "m1_shape should be connected to m2_shape1" + ); + + // Expected disconnection + assert!( + !component_lookup.are_connected(&m1_shape, &m2_shape3), + "m1_shape should not be connected to m2_shape3 (isolated)" + ); + assert!( + !component_lookup.are_connected(&m1_shape, &outside_shape), + "m1_shape should not be connected to outside_shape" + ); + + // Double check number of connected components in library + assert_eq!( + components_vec.len(), + 2, + "Expected 2 total connected components." + ); + } +} diff --git a/libs/layir/src/lib.rs b/libs/layir/src/lib.rs index 67831a0f..b2216a5d 100644 --- a/libs/layir/src/lib.rs +++ b/libs/layir/src/lib.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] +pub mod connectivity; pub mod id; use std::{