Skip to content

Commit 981358a

Browse files
authored
feat(connectivity): implement connectivity analysis for LayIR libraries (#679)
1 parent d4718d3 commit 981358a

File tree

4 files changed

+253
-0
lines changed

4 files changed

+253
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/layir/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ thiserror = "2"
1313
enumify = { version = "0.2.1", registry = "substrate", path = "../enumify" }
1414
uniquify = { version = "0.4.0", registry = "substrate", path = "../uniquify" }
1515
geometry = { version = "0.7.1", registry = "substrate", path = "../geometry" }
16+
aph_disjoint_set = "0.1.0"
1617

libs/layir/src/connectivity.rs

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
use crate::CellId;
2+
use crate::Instance;
3+
use aph_disjoint_set::DisjointSet;
4+
use geometry::bbox::Bbox;
5+
use geometry::rect::Rect;
6+
use std::collections::{HashMap, HashSet};
7+
use std::hash::Hash;
8+
9+
use crate::Element;
10+
use crate::{Cell, Library, Shape, TransformRef, Transformation};
11+
12+
/// Returns true if two shapes overlap on a flat plane, and false otherwise.
13+
fn intersect_shapes<L>(shape1: &Shape<L>, shape2: &Shape<L>) -> bool {
14+
let shape1_bbox: Rect = shape1.bbox_rect();
15+
let shape2_bbox: Rect = shape2.bbox_rect();
16+
shape1_bbox.intersection(shape2_bbox).is_some()
17+
}
18+
19+
/// Returns a vector of all shapes from a given cell instance and its children
20+
/// with their coordinates transformed into the coordinate system of the instance's parent.
21+
fn flatten_instance<L>(inst: &Instance, lib: &Library<L>) -> Vec<Shape<L>>
22+
where
23+
L: Connectivity + Clone,
24+
{
25+
let mut ret: Vec<Shape<L>> = vec![];
26+
27+
let cellid: CellId = inst.child();
28+
let transform: Transformation = inst.transformation();
29+
30+
// Add all shape elements directly from child cell after applying the instances transformation
31+
for elem in lib.cell(cellid).elements() {
32+
if let Element::Shape(shape) = elem {
33+
let transformed_shape = shape.transform_ref(transform);
34+
ret.push(transformed_shape);
35+
}
36+
}
37+
38+
// Recursively flatten child instances and apply cumulative transformations
39+
for instance in lib.cell(cellid).instances() {
40+
let mut flattened_shapes = flatten_instance::<L>(instance.1, lib);
41+
for flattened_shape in &mut flattened_shapes {
42+
*flattened_shape = flattened_shape.transform_ref(transform);
43+
}
44+
ret.append(&mut flattened_shapes);
45+
}
46+
47+
ret
48+
}
49+
50+
/// Returns a vector of all shapes from a single cell's transformed child instances and itself.
51+
fn flatten_cell<L>(cell: &Cell<L>, lib: &Library<L>) -> Vec<Shape<L>>
52+
where
53+
L: Connectivity + Clone,
54+
{
55+
let mut ret: Vec<Shape<L>> = vec![];
56+
57+
// No transformation needed
58+
for elem in cell.elements() {
59+
if let Element::Shape(shape) = elem {
60+
ret.push(shape.clone());
61+
}
62+
}
63+
64+
for inst in cell.instances() {
65+
for flattened_shape in flatten_instance::<L>(inst.1, lib) {
66+
ret.push(flattened_shape);
67+
}
68+
}
69+
70+
ret
71+
}
72+
73+
pub trait Connectivity: Sized + PartialEq + Eq + Clone + Hash {
74+
fn connected_layers(&self) -> Vec<Self>;
75+
76+
/// Returns a vector of layers connected to a given layer.
77+
fn connected(&self, other: &Self) -> bool {
78+
self.connected_layers().contains(other)
79+
}
80+
81+
/// Returns a vector of unique hashsets of all connected groups of connected child shapes in a given cell.
82+
fn connected_components(cell: &Cell<Self>, lib: &Library<Self>) -> Vec<HashSet<Shape<Self>>>
83+
where
84+
Self: Clone,
85+
{
86+
// All sub-shapes contained in given cell
87+
let all_shapes = flatten_cell::<Self>(cell, lib);
88+
let mut djs = DisjointSet::new(all_shapes.len());
89+
90+
// Build disjoint sets based on overlap and layer connectivity
91+
for (start_index, start_shape) in all_shapes.clone().into_iter().enumerate() {
92+
for (other_index, other_shape) in all_shapes.clone().into_iter().enumerate() {
93+
if start_index != other_index
94+
&& intersect_shapes::<Self>(&start_shape, &other_shape)
95+
&& start_shape.layer().connected(other_shape.layer())
96+
{
97+
djs.union(start_index, other_index);
98+
}
99+
}
100+
}
101+
102+
let mut component_map: HashMap<usize, HashSet<Shape<Self>>> = HashMap::new();
103+
104+
for (start_index, start_shape) in all_shapes.into_iter().enumerate() {
105+
let root: usize = djs.get_root(start_index).into_inner();
106+
component_map.entry(root).or_default().insert(start_shape);
107+
}
108+
109+
component_map.into_values().collect()
110+
}
111+
}
112+
113+
#[cfg(test)]
114+
mod tests {
115+
use super::*;
116+
use crate::{Cell, Instance, LibraryBuilder, Shape};
117+
use geometry::rect::Rect;
118+
use std::collections::{HashMap, HashSet};
119+
120+
// This struct helps check if two shapes are connected after connected_components has been run
121+
struct ComponentLookup<L> {
122+
shape_to_component_id: HashMap<Shape<L>, usize>,
123+
}
124+
125+
impl<L> ComponentLookup<L>
126+
where
127+
L: Connectivity + Clone,
128+
{
129+
/// Creates a new ComponentLookup from a vector of connected components.
130+
fn new(components: Vec<HashSet<Shape<L>>>) -> Self {
131+
let mut shape_to_component_id = HashMap::new();
132+
for (component_id, component_set) in components.into_iter().enumerate() {
133+
for shape in component_set.into_iter() {
134+
shape_to_component_id.insert(shape, component_id);
135+
}
136+
}
137+
ComponentLookup {
138+
shape_to_component_id,
139+
}
140+
}
141+
142+
/// Returns true if both shapes are found and belong to the same component, and false otherwise.
143+
fn are_connected(&self, s1: &Shape<L>, s2: &Shape<L>) -> bool {
144+
let comp_id1 = self.shape_to_component_id.get(s1);
145+
let comp_id2 = self.shape_to_component_id.get(s2);
146+
147+
match (comp_id1, comp_id2) {
148+
// If both shapes are found, check if their component IDs are the same
149+
(Some(&id1), Some(&id2)) => id1 == id2,
150+
_ => false,
151+
}
152+
}
153+
}
154+
155+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
156+
enum Layer {
157+
Met1,
158+
Via1,
159+
Met2,
160+
Outside,
161+
}
162+
163+
impl Connectivity for Layer {
164+
fn connected_layers(&self) -> Vec<Self> {
165+
match self {
166+
Self::Met1 => vec![Self::Met1, Self::Via1],
167+
Self::Via1 => vec![Self::Met1, Self::Via1, Self::Met2],
168+
Self::Met2 => vec![Self::Via1, Self::Met2],
169+
Self::Outside => vec![],
170+
}
171+
}
172+
}
173+
174+
#[test]
175+
fn test_complete() {
176+
let mut small_cell: Cell<Layer> = Cell::new("small cell test");
177+
let mut big_cell: Cell<Layer> = Cell::new("big cell test");
178+
let mut lib_builder: LibraryBuilder<Layer> = LibraryBuilder::new();
179+
180+
// Build small cell first and add to big cell
181+
let m2_shape1 = Shape::new(Layer::Met2, Rect::from_sides(0, 0, 30, 30));
182+
small_cell.add_element(m2_shape1.clone());
183+
184+
lib_builder.add_cell(small_cell.clone());
185+
let small_cell_id = lib_builder.cells().next().unwrap().0;
186+
187+
let small_cell_instance = Instance::new(small_cell_id, "small_cell_test");
188+
big_cell.add_instance(small_cell_instance);
189+
190+
// Build big cell
191+
let m1_shape = Shape::new(Layer::Met1, Rect::from_sides(0, 0, 100, 100));
192+
let v1_shape = Shape::new(Layer::Via1, Rect::from_sides(10, 10, 100, 100));
193+
let m2_shape2 = Shape::new(Layer::Met2, Rect::from_sides(10, 10, 50, 50));
194+
let m2_shape3 = Shape::new(Layer::Met2, Rect::from_sides(1000, 1000, 5000, 5000));
195+
big_cell.add_element(m1_shape.clone());
196+
big_cell.add_element(v1_shape.clone());
197+
big_cell.add_element(m2_shape2.clone());
198+
big_cell.add_element(m2_shape3.clone());
199+
200+
lib_builder.add_cell(big_cell.clone());
201+
202+
// Build fixed library
203+
let lib = lib_builder.clone().build().unwrap();
204+
205+
// Add an outside shape for testing
206+
let outside_shape = Shape::new(Layer::Outside, Rect::from_sides(0, 0, 100, 100));
207+
208+
// Find all connected components of big_cell's child shapes
209+
let components_vec = Layer::connected_components(&big_cell, &lib);
210+
211+
let component_lookup = ComponentLookup::new(components_vec.clone());
212+
213+
// Expected connections
214+
assert!(
215+
component_lookup.are_connected(&m1_shape, &v1_shape),
216+
"m1_shape should be connected to v1_shape"
217+
);
218+
assert!(
219+
component_lookup.are_connected(&m1_shape, &m2_shape2),
220+
"m1_shape should be connected to m2_shape2"
221+
);
222+
assert!(
223+
component_lookup.are_connected(&m1_shape, &m2_shape1), // m2_shape1 is from the instance
224+
"m1_shape should be connected to m2_shape1"
225+
);
226+
227+
// Expected disconnection
228+
assert!(
229+
!component_lookup.are_connected(&m1_shape, &m2_shape3),
230+
"m1_shape should not be connected to m2_shape3 (isolated)"
231+
);
232+
assert!(
233+
!component_lookup.are_connected(&m1_shape, &outside_shape),
234+
"m1_shape should not be connected to outside_shape"
235+
);
236+
237+
// Double check number of connected components in library
238+
assert_eq!(
239+
components_vec.len(),
240+
2,
241+
"Expected 2 total connected components."
242+
);
243+
}
244+
}

libs/layir/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(dead_code)]
22

3+
pub mod connectivity;
34
pub mod id;
45

56
use std::{

0 commit comments

Comments
 (0)