OASIS to GDSII Conversion
Detailed guide for converting OASIS files to GDSII format.
Basic Conversion
#![allow(unused)] fn main() { use laykit::{OASISFile, converter}; let oasis = OASISFile::read_from_file("input.oas")?; let gds = converter::oasis_to_gdsii(&oasis)?; gds.write_to_file("output.gds")?; }
What Gets Converted
File-Level
- OASIS version → Stored as GDSII version (typically 3 or 5)
- Unit → OASIS unit becomes GDSII database unit
- Cells → Converted to GDSII structures
- Name tables → Expanded to actual strings
Cell-Level
- Cell name → Structure name
- Timestamps → Set to current time (OASIS doesn't always store timestamps)
- Elements → Converted with type mapping
Element-Level Mapping
Rectangle → Boundary
OASIS rectangles become 5-vertex closed polygons:
#![allow(unused)] fn main() { // OASIS Rectangle Rectangle { layer: 1, datatype: 0, x: 0, y: 0, width: 100, height: 50, } // Converts to GDSII Boundary Boundary { layer: 1, datatype: 0, xy: vec![ (0, 0), (100, 0), (100, 50), (0, 50), (0, 0), // Closed polygon ], } }
Polygon → Boundary
Direct conversion:
#![allow(unused)] fn main() { // OASIS Polygon Polygon { layer: 2, datatype: 0, x: 0, y: 0, points: vec![(0,0), (100,0), (50,100), (0,0)], } // Converts to GDSII Boundary Boundary { layer: 2, datatype: 0, xy: vec![(0,0), (100,0), (50,100), (0,0)], } }
Note: OASIS polygon points are relative to (x, y), so they're adjusted to absolute coordinates.
Path → Path
Paths are directly converted:
#![allow(unused)] fn main() { // OASIS Path OPath { layer: 3, datatype: 0, width: 10, points: vec![(0,0), (100,100)], start_extension: 5, end_extension: 5, } // Converts to GDSII Path GPath { layer: 3, datatype: 0, pathtype: 0, width: Some(10), xy: vec![(0,0), (100,100)], // Extensions handled by pathtype } }
Trapezoid → Boundary
Trapezoids are converted to 5-vertex polygons:
#![allow(unused)] fn main() { // OASIS Trapezoid Trapezoid { x: 0, y: 0, width: 100, height: 50, delta_a: 10, // Top offset delta_b: -10, // Bottom offset } // Converts to GDSII Boundary Boundary { xy: vec![ (0, 0), (90, 0), // width + delta_b (110, 50), // width + delta_a at top (10, 50), // delta_a (0, 0), ], } }
CTrapezoid → Boundary
Constrained trapezoids are also converted to polygons:
#![allow(unused)] fn main() { // OASIS CTrapezoid CTrapezoid { layer: 4, ctrapezoid_type: 0, width: 100, height: 50, ... } // Converts to GDSII Boundary with appropriate vertices }
Circle → Boundary (Approximated)
Circles are approximated with polygons:
#![allow(unused)] fn main() { // OASIS Circle Circle { layer: 5, x: 500, y: 500, radius: 100, } // Converts to GDSII Boundary with 32 vertices (approximation) Boundary { layer: 5, xy: vec![ // 32 points forming a circle // calculated as: (x + r*cos(θ), y + r*sin(θ)) ], } }
Note: The number of segments can be adjusted for accuracy vs. file size.
Text → Text
Direct text conversion:
#![allow(unused)] fn main() { // OASIS Text OText { layer: 6, texttype: 0, string: "LABEL", x: 1000, y: 1000, } // Converts to GDSII Text GText { layer: 6, texttype: 0, string: "LABEL", xy: (1000, 1000), } }
Placement → StructRef or ArrayRef
Single placements:
#![allow(unused)] fn main() { // OASIS Placement (no repetition) Placement { cell_name: "SUBCELL", x: 1000, y: 2000, magnification: Some(1.5), angle: Some(90.0), mirror_x: true, repetition: None, } // Converts to GDSII StructRef StructRef { sname: "SUBCELL", xy: (1000, 2000), strans: Some(STrans { reflect_x: true, magnification: Some(1.5), angle: Some(90.0), ... }), } }
Placements with repetition:
#![allow(unused)] fn main() { // OASIS Placement with Repetition Placement { cell_name: "SUBCELL", x: 0, y: 0, repetition: Some(Repetition { x_dimension: 10, y_dimension: 5, x_space: 1000, y_space: 2000, }), } // Converts to GDSII ArrayRef ArrayRef { sname: "SUBCELL", columns: 10, rows: 5, xy: [ (0, 0), // Reference point (10000, 0), // Column spacing (10 * 1000) (0, 10000), // Row spacing (5 * 2000) ], } }
Units Conversion
#![allow(unused)] fn main() { // OASIS unit oasis.unit = 1e-9; // 1nm // GDSII units conversion gds.units = (1e-6, 1e-9); // 1µm user unit, 1nm database unit // ^ ^ // user database (from OASIS) }
The user unit is set to 1000× the database unit by default.
Timestamp Handling
Since OASIS may not store modification times:
#![allow(unused)] fn main() { // All structures get current timestamp structure.creation_time = GDSTime::now(); structure.modification_time = GDSTime::now(); }
Name Table Expansion
OASIS name tables are expanded:
#![allow(unused)] fn main() { // OASIS has compact name tables oasis.names.cell_names: {0: "CELL1", 1: "CELL2"} oasis.names.text_strings: {0: "LABEL"} // Expanded in GDSII as actual strings gds.structures[0].name = "CELL1"; gds.structures[1].name = "CELL2"; // Text elements contain "LABEL" directly }
Coordinate Conversion
OASIS uses 64-bit coordinates, GDSII uses 32-bit:
#![allow(unused)] fn main() { // OASIS coordinates (i64) let oasis_x: i64 = 1_000_000_000; // Converted to GDSII (i32) let gds_x: i32 = oasis_x as i32; // May truncate if too large! }
Warning: Very large OASIS coordinates may overflow GDSII's 32-bit limit.
Complete Example with Validation
#![allow(unused)] fn main() { use laykit::{OASISFile, converter, GDSElement}; fn convert_and_validate(input_path: &str, output_path: &str) -> Result<(), Box<dyn std::error::Error>> { // Read OASIS let oasis = OASISFile::read_from_file(input_path)?; println!("OASIS Analysis:"); println!(" Version: {}", oasis.version); println!(" Unit: {} meters", oasis.unit); println!(" Cells: {}", oasis.cells.len()); let mut total_elements = 0; let mut rectangles = 0; let mut circles = 0; let mut trapezoids = 0; for cell in &oasis.cells { total_elements += cell.elements.len(); for element in &cell.elements { match element { OASISElement::Rectangle(_) => rectangles += 1, OASISElement::Circle(_) => circles += 1, OASISElement::Trapezoid(_) => trapezoids += 1, OASISElement::CTrapezoid(_) => trapezoids += 1, _ => {} } } } println!(" Total elements: {}", total_elements); println!(" Rectangles: {}", rectangles); println!(" Circles: {} (will be approximated)", circles); println!(" Trapezoids: {}", trapezoids); // Convert let gds = converter::oasis_to_gdsii(&oasis)?; // Analyze GDSII output println!("\nGDSII Output:"); println!(" Library: {}", gds.library_name); println!(" Structures: {}", gds.structures.len()); println!(" Units: {}µm user, {}nm database", gds.units.0 * 1e6, gds.units.1 * 1e9); let mut gds_elements = 0; let mut boundaries = 0; for structure in &gds.structures { gds_elements += structure.elements.len(); for element in &structure.elements { if let GDSElement::Boundary(_) = element { boundaries += 1; } } } println!(" Total elements: {}", gds_elements); println!(" Boundaries: {} (includes converted shapes)", boundaries); // Write output gds.write_to_file(output_path)?; println!("\n✅ Conversion complete: {}", output_path); Ok(()) } }
Handling Edge Cases
Large Coordinates
#![allow(unused)] fn main() { // Check for coordinate overflow if oasis_coord > i32::MAX as i64 { eprintln!("Warning: Coordinate {} exceeds GDSII limit", oasis_coord); // May need to scale design or split into multiple files } }
Complex Repetitions
#![allow(unused)] fn main() { // Large arrays might create many instances if repetition.x_dimension * repetition.y_dimension > 10000 { println!("Warning: Large array ({} instances)", repetition.x_dimension * repetition.y_dimension); } }
Circle Approximation Quality
#![allow(unused)] fn main() { // For critical circles, verify approximation: // - Check vertex count (typically 32-64) // - Ensure radius error is acceptable // - Consider increasing segments for large circles }
Tips for Best Results
- Check coordinate ranges - Ensure they fit in 32-bit integers
- Verify circle approximations - May need visual inspection
- Test in target tool - Load converted GDSII in your EDA software
- Compare file sizes - GDSII will be larger than OASIS
- Preserve metadata - Add comments about conversion if needed
File Size Comparison
GDSII files are typically larger than OASIS:
#![allow(unused)] fn main() { // Example file sizes: // design.oas: 2.3 MB // design.gds: 10.5 MB (4.5× larger) }
This is expected due to GDSII's less efficient encoding.