memea/
gds.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! GDS layout file processing for MemEA component enclosure calculation.
//!
//! This module provides functionality to parse GDS layout files, inspect all
//! layers, and calculate enclosure size based on the relative difference
//! between the cell footprint and PR boundary.
use gds21::{GdsElement, GdsLibrary};
use std::collections::{HashMap, HashSet};
use thiserror::Error;

use crate::db::Dims;
use crate::{errorln, vprintln, Float, MemeaError};

/// Errors that can occur during GDS layout processing.
#[derive(Debug, Error)]
pub enum GdsError {
    /// Indicates that a requested cell was not found in the GDS library.
    #[error("Cell not found: {0}")]
    InvalidCell(String),
    /// Indicates that a GDS element contains no geometry data.
    #[error("Inspected element is empty: {0}")]
    EmptyElement(String),
}

/// Creates a hashmap of GDS library cells indexed by name for fast lookup.
///
/// This function transforms a GDS library into a HashMap where each cell name
/// maps to its corresponding vector of geometric elements, enabling efficient
/// cell lookup during dimension computation.
///
/// # Arguments
/// * `lib` - GDS library containing cell structures and elements
///
/// # Returns
/// HashMap mapping cell names to their geometric elements
///
/// # Examples
/// ```no_run
/// use gds21::GdsLibrary;
/// use memea::gds::hash_lib;
///
/// let library = GdsLibrary::load("layout.gds").expect("Failed to load GDS");
/// let cell_map = hash_lib(library);
/// println!("Loaded {} cells from GDS", cell_map.len());
/// ```
pub fn hash_lib(lib: GdsLibrary) -> HashMap<String, Vec<GdsElement>> {
    // Hash cells by name for fast lookup
    lib.structs.into_iter().map(|s| (s.name, s.elems)).collect()
}

/// Computes enclosure requirements from GDS geometry elements.
///
/// This function analyzes the boundary polygons in a GDS cell to determine
/// the enclosure margins needed around the core component dimensions. It
/// calculates the bounding box of all geometry and computes the difference
/// between the total span and the core dimensions.
///
/// # Arguments
/// * `elems` - Vector of GDS elements containing boundary polygons
/// * `w` - Core component width in micrometers
/// * `h` - Core component height in micrometers
/// * `units` - GDS unit conversion factor (database units to meters)
/// * `verbose` - Whether to print detailed computation information
///
/// # Returns
/// * `Ok((enc_x, enc_y))` - Horizontal and vertical enclosure margins
/// * `Err(MemeaError)` - Error if no valid geometry is found
fn compute_enc(
    elems: &Vec<GdsElement>,
    w: Float,
    h: Float,
    units: f64,
    verbose: bool,
) -> Result<(Float, Float), MemeaError> {
    if elems.is_empty() {
        errorln!("No geometry data for cell; cannot compute enclosure.");
        return Ok((0.0, 0.0));
    }

    let mut boundaries: usize = 0;
    let mut layers = HashSet::new();

    let mut iter = elems
        .iter()
        .filter_map(|elem| {
            if let GdsElement::GdsBoundary(b) = elem {
                boundaries += 1;
                layers.insert(b.layer);
                Some(b.xy.iter())
            } else {
                None
            }
        })
        .flatten();

    let first = iter
        .next()
        .ok_or(GdsError::EmptyElement(format!("{elems:?}")))?;
    let mut min_x = first.x;
    let mut max_x = first.x;
    let mut min_y = first.y;
    let mut max_y = first.y;

    for p in iter.skip(1) {
        if p.x < min_x {
            min_x = p.x;
        }
        if p.x > max_x {
            max_x = p.x;
        }
        if p.y < min_y {
            min_y = p.y;
        }
        if p.y > max_y {
            max_y = p.y;
        }
    }

    let scale = units as f32 / 1e-6;
    let (span_x, span_y) = (
        (max_x - min_x) as f32 * scale,
        (max_y - min_y) as f32 * scale,
    );
    let (enc_x, enc_y) = ((span_x - w) / 2.0, (span_y - h) / 2.0);

    vprintln!(
        verbose,
        "Computed enclosure [{:.4}, {:.4}] from {} polygons across {} layers",
        enc_x,
        enc_y,
        boundaries,
        layers.len()
    );

    Ok((enc_x as Float, enc_y as Float))
}

/// Augments component dimensions with enclosure data from GDS layout.
///
/// This function looks up a cell in the GDS library hashmap and computes
/// the required enclosure margins by analyzing the cell's geometry. It
/// returns a complete `Dims` structure with both core dimensions and
/// enclosure requirements.
///
/// # Arguments
/// * `map` - HashMap of cell names to GDS elements (from `hash_lib`)
/// * `cell` - Name of the cell to analyze
/// * `w` - Core component width in micrometers
/// * `h` - Core component height in micrometers
/// * `units` - GDS unit conversion factor
/// * `verbose` - Whether to show detailed computation output
///
/// # Returns
/// * `Ok(Dims)` - Complete dimensions with enclosure data
/// * `Err(MemeaError)` - Error during geometry analysis
///
/// # Examples
/// ```no_run
/// use memea::gds::{hash_lib, augment_dims};
/// use gds21::GdsLibrary;
///
/// let library = GdsLibrary::load("cells.gds").expect("Failed to load GDS");
/// let cell_map = hash_lib(library);
/// let units = 1e-9; // 1 nm database units
///
/// let dims = augment_dims(&cell_map, "sram_6t", 0.5, 0.8, units, true)
///     .expect("Failed to compute dimensions");
/// println!("Cell area: {:.2} μm²", dims.area((1, 1)));
/// ```
pub fn augment_dims(
    map: &HashMap<String, Vec<GdsElement>>,
    cell: &str,
    w: Float,
    h: Float,
    units: f64,
    verbose: bool,
) -> Result<Dims, MemeaError> {
    // Lookup cell
    if let Some(elems) = map.get(cell) {
        let (enc_x, enc_y) = compute_enc(elems, w, h, units, verbose)?;
        Ok(Dims::from(w, h, enc_x, enc_y))
    } else {
        errorln!(
            "Could not find matching cell {} in GDS database; cannot compute enclosure",
            cell
        );
        Ok(Dims::from(w, h, 0.0, 0.0))
    }
}