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))
}
}