From e505969a45bb123c63bd59914d78f9471cf88ab2 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Mon, 17 Jan 2022 10:54:53 -0800 Subject: [PATCH 01/28] Initial Flattening of . Break out module. --- layout21raw/src/geom.rs | 380 ++++++++++++++++++++++++++++++++++++++++ layout21raw/src/lib.rs | 256 +++++++++------------------ 2 files changed, 461 insertions(+), 175 deletions(-) create mode 100644 layout21raw/src/geom.rs diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs new file mode 100644 index 0000000..db4f36b --- /dev/null +++ b/layout21raw/src/geom.rs @@ -0,0 +1,380 @@ +//! +//! # Geometry Module +//! +//! Defines the core geometric types including [Point], [Shape], and [Transform], +//! and their core operations. +//! + +// Std-Lib +use std::convert::{TryFrom, TryInto}; + +// Crates.io +use serde::{Deserialize, Serialize}; + +/// # Point in two-dimensional layout-space +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct Point { + pub x: isize, + pub y: isize, +} +impl Point { + /// Create a new [Point] from (x,y) coordinates + pub fn new(x: isize, y: isize) -> Self { + Self { x, y } + } + /// Create a new [Point] which serves as an offset in direction `dir` + pub fn offset(val: isize, dir: Dir) -> Self { + match dir { + Dir::Horiz => Self { x: val, y: 0 }, + Dir::Vert => Self { x: 0, y: val }, + } + } + /// Create a new point shifted by `x` in the x-dimension and by `y` in the y-dimension + pub fn shift(&self, p: &Point) -> Point { + Point { + x: p.x + self.x, + y: p.y + self.y, + } + } + /// Create a new point scaled by `p.x` in the x-dimension and by `p.y` in the y-dimension + pub fn scale(&self, p: &Point) -> Point { + Point { + x: p.x * self.x, + y: p.y * self.y, + } + } + /// Get the coordinate associated with direction `dir` + pub fn coord(&self, dir: Dir) -> isize { + match dir { + Dir::Horiz => self.x, + Dir::Vert => self.y, + } + } + /// Create a ne [Point], transformed from our original location by `transform` + /// Coordinate transforms are applied in floating-point format, + /// largely for rotations, and then rounded to the nearest integer. + pub fn transform(&self, trans: &Transform) -> Point { + let xf = self.x as f64; + let yf = self.y as f64; + let x = trans.a[0][0] * xf + trans.a[0][1] * yf + trans.b[0]; + let y = trans.a[1][0] * xf + trans.a[1][1] * yf + trans.b[1]; + Self { + x: x.round() as isize, + y: y.round() as isize, + } + } +} +/// Direction Enumeration +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum Dir { + Horiz, + Vert, +} +impl Dir { + /// Whichever direction we are, return the other one. + pub fn other(self) -> Self { + match self { + Self::Horiz => Self::Vert, + Self::Vert => Self::Horiz, + } + } +} +impl std::ops::Not for Dir { + type Output = Self; + /// Exclamation Operator returns the opposite direction + fn not(self) -> Self::Output { + self.other() + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum Shape { + Rect { p0: Point, p1: Point }, + Poly { pts: Vec }, + Path { width: usize, pts: Vec }, +} +impl Shape { + /// Retrieve our "origin", or first [Point] + pub fn point0(&self) -> &Point { + match *self { + Shape::Rect { ref p0, p1: _ } => p0, + Shape::Poly { ref pts } => &pts[0], + Shape::Path { ref pts, .. } => &pts[0], + } + } + /// Calculate our center-point + pub fn center(&self) -> Point { + match *self { + Shape::Rect { ref p0, ref p1 } => Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2), + Shape::Path { ref pts, .. } => { + // Place on the center of the first segment + let p0 = &pts[0]; + let p1 = &pts[1]; + Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) + } + Shape::Poly { .. } => { + unimplemented!("Shape::Poly/Path::center"); + } + } + } + /// Indicate whether this shape is (more or less) horizontal or vertical + /// Primarily used for orienting label-text + pub fn orientation(&self) -> Dir { + match *self { + Shape::Rect { ref p0, ref p1 } => { + if (p1.x - p0.x).abs() < (p1.y - p0.y).abs() { + return Dir::Vert; + } + Dir::Horiz + } + // Polygon and Path elements always horizontal, at least for now + Shape::Poly { .. } | Shape::Path { .. } => Dir::Horiz, + } + } + /// Apply matrix-vector [Tranform] `trans`. + /// Creates a new shape at a location equal to the transformation of our own. + pub fn transform(&self, trans: &Transform) -> Shape { + match *self { + Shape::Rect { ref p0, ref p1 } => Shape::Rect { + p0: p0.transform(trans), + p1: p1.transform(trans), + }, + Shape::Poly { ref pts } => Shape::Poly { + pts: pts.iter().map(|p| p.transform(trans)).collect(), + }, + Shape::Path { ref pts, ref width } => Shape::Path { + pts: pts.iter().map(|p| p.transform(trans)).collect(), + width: *width, + }, + } + } + /// Shift coordinates by the (x,y) values specified in `pt` + pub fn shift(&mut self, pt: &Point) { + match *self { + Shape::Rect { + ref mut p0, + ref mut p1, + } => { + p0.x += pt.x; + p0.y += pt.y; + p1.x += pt.x; + p1.y += pt.y; + } + Shape::Poly { ref mut pts } => { + for p in pts.iter_mut() { + p.x += pt.x; + p.y += pt.y; + } + } + Shape::Path { ref mut pts, .. } => { + for p in pts.iter_mut() { + p.x += pt.x; + p.y += pt.y; + } + } + } + } + /// Boolean indication of whether we contain point `pt` + pub fn contains(&self, pt: &Point) -> bool { + match self { + Shape::Rect { ref p0, ref p1 } => { + p0.x.min(p1.x) <= pt.x + && p0.x.max(p1.x) >= pt.x + && p0.y.min(p1.y) <= pt.y + && p0.y.max(p1.y) >= pt.y + } + Shape::Poly { .. } => false, // FIXME! todo!(), + Shape::Path { ref width, ref pts } => { + // Break into segments, and check for intersection with each + // Probably not the most efficient way to do this, but a start. + // Only "Manhattan paths", i.e. those with segments solely running vertically or horizontally, are supported. + // FIXME: even with this method, there are some small pieces at corners which we'll miss. + // Whether these are relevant in real life, tbd. + let width = isize::try_from(*width).unwrap(); // FIXME: probably store these signed, check them on creation + for k in 0..pts.len() - 1 { + let rect = if pts[k].x == pts[k + 1].x { + Shape::Rect { + p0: Point::new(pts[k].x - width / 2, pts[k].y), + p1: Point::new(pts[k].x + width / 2, pts[k + 1].y), + } + } else if pts[k].y == pts[k + 1].y { + Shape::Rect { + p0: Point::new(pts[k].x, pts[k].y - width / 2), + p1: Point::new(pts[k + 1].x, pts[k].y + width / 2), + } + } else { + unimplemented!("Unsupported Non-Manhattan Path") + }; + if rect.contains(pt) { + return true; + } + } + false + } + } + } +} + +/// # Matrix-Vector Transformation +/// +/// 2x2 rotation-matrix and two-entry translation vector, +/// used for relative movement of [Point]s and [Shape]s. +/// +#[derive(Debug, Default, Clone)] +pub struct Transform { + /// Rotation / Transformation Matrix + /// Represented in row-major order + pub a: [[f64; 2]; 2], + /// X-Y Translation + pub b: [f64; 2], +} +impl Transform { + /// The identity transform, leaving any transformed object unmodified + pub fn identity() -> Self { + Self { + a: [[1., 0.], [0., 1.]], + b: [0., 0.], + } + } + /// Translation by (x,y) + pub fn translate(x: f64, y: f64) -> Self { + Self { + a: [[1., 0.], [0., 1.]], + b: [x, y], + } + } + /// A transform to rotate by `angle` degrees + pub fn rotate(angle: f64) -> Self { + let sin = angle.to_radians().sin(); + let cos = angle.to_radians().cos(); + Self { + a: [[cos, -sin], [sin, cos]], + b: [0., 0.], + } + } + /// A transform to reflect about the x-axis + pub fn reflect_vert() -> Self { + Self { + a: [[1., 0.], [0., -1.]], + b: [0., 0.], + } + } + /// Create a transform from instance fields: location, rotation, and reflection + pub fn from_instance(loc: &Point, reflect_vert: bool, angle: Option) -> Self { + let b = [loc.x as f64, loc.y as f64]; + let (mut sin, mut cos) = (0., 1.); + if let Some(angle) = angle { + sin = angle.to_radians().sin(); + cos = angle.to_radians().cos(); + } + let cos_refl = if reflect_vert { -cos } else { cos }; + let a = [[cos, -sin], [sin, cos_refl]]; + Self { a, b } + } + /// Create a new [Transform] that is the cascade of `parent` and `child`. + /// + /// "Parents" and "children" refer to typical layout-instance hierarchies, + /// in which each layer of instance has a nested set of transformations relative to its top-level parent. + /// + /// Note this operation *is not* commutative. + /// For example the set of transformations: + /// * (a) Reflect vertically, then + /// * (b) Translate by (1,1) + /// * (c) Place a point at (local coordinate) (1,1) + /// Lands said point at (2,-2) in top-level space, + /// whereas reversing the order of (a) and (b) lands it at (2,0). + /// + pub fn cascade(parent: &Transform, child: &Transform) -> Transform { + // The result-transform's origin is the parent's origin, + // plus the parent-transformed child's origin + let mut b = matvec(&parent.a, &child.b); + b[0] += parent.b[0]; + b[1] += parent.b[1]; + // And the cascade-matrix is the product of the parent's and child's + let a = matmul(&parent.a, &child.a); + Self { a, b } + } +} +/// Multiply 2x2 matrices, returning a new 2x2 matrix +fn matmul(a: &[[f64; 2]; 2], b: &[[f64; 2]; 2]) -> [[f64; 2]; 2] { + [ + [ + a[0][0] * b[0][0] + a[0][1] * b[1][0], + a[0][0] * b[0][1] + a[0][1] * b[1][1], + ], + [ + a[1][0] * b[0][0] + a[1][1] * b[1][0], + a[1][0] * b[0][1] + a[1][1] * b[1][1], + ], + ] +} +/// Multiply a 2x2 matrix by a 2-entry vector, returning a new 2-entry vector +fn matvec(a: &[[f64; 2]; 2], b: &[f64; 2]) -> [f64; 2] { + [ + a[0][0] * b[0] + a[0][1] * b[1], + a[1][0] * b[0] + a[1][1] * b[1], + ] +} + +#[cfg(test)] +pub mod tests { + use super::*; + #[test] + fn transform_identity() { + let shape1 = Shape::Rect { + p0: Point::new(0, 0), + p1: Point::new(1, 1), + }; + let trans = Transform::identity(); + let shape2 = shape1.transform(&trans); + assert_eq!(shape2, shape1); + } + #[test] + fn transform_rotate() { + let shape1 = Shape::Rect { + p0: Point::new(0, 0), + p1: Point::new(1, 1), + }; + let trans = Transform::rotate(90.); + let shape2 = shape1.transform(&trans); + assert_eq!( + shape2, + Shape::Rect { + p0: Point::new(0, 0), + p1: Point::new(-1, 1), + } + ); + let shape3 = shape2.transform(&trans); + assert_eq!( + shape3, + Shape::Rect { + p0: Point::new(0, 0), + p1: Point::new(-1, -1), + } + ); + let shape4 = shape3.transform(&trans); + assert_eq!( + shape4, + Shape::Rect { + p0: Point::new(0, 0), + p1: Point::new(1, -1), + } + ); + let shape0 = shape4.transform(&trans); + assert_eq!(shape0, shape1); + } + #[test] + fn test_cascade1() { + let trans1 = Transform::reflect_vert(); + let trans2 = Transform::translate(1., 1.); + + let p = Point::new(1, 1); + let cascade1 = Transform::cascade(&trans1, &trans2); + let pc1 = p.transform(&cascade1); + assert_eq!(pc1, Point::new(2, -2)); + + let cascade2 = Transform::cascade(&trans2, &trans1); + let pc1 = p.transform(&cascade2); + assert_eq!(pc1, Point::new(2, 0)); + } +} diff --git a/layout21raw/src/lib.rs b/layout21raw/src/lib.rs index 8cfad35..131e783 100644 --- a/layout21raw/src/lib.rs +++ b/layout21raw/src/lib.rs @@ -8,9 +8,8 @@ // Std-Lib use std::collections::{HashMap, HashSet}; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; use std::hash::Hash; -use std::ops::Not; // Crates.io use serde::{Deserialize, Serialize}; @@ -20,6 +19,8 @@ use slotmap::{new_key_type, SlotMap}; pub use layout21utils as utils; use utils::ErrorContext; use utils::{Ptr, PtrList}; +pub mod geom; +pub use geom::{Dir, Point, Shape, Transform}; // Optional-feature modules #[cfg(feature = "gds")] @@ -229,69 +230,6 @@ impl SiUnits { } } -/// # Point in two-dimensional layout-space -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] -pub struct Point { - pub x: isize, - pub y: isize, -} -impl Point { - /// Create a new [Point] from (x,y) coordinates - pub fn new(x: isize, y: isize) -> Self { - Self { x, y } - } - /// Create a new [Point] which serves as an offset in direction `dir` - pub fn offset(val: isize, dir: Dir) -> Self { - match dir { - Dir::Horiz => Self { x: val, y: 0 }, - Dir::Vert => Self { x: 0, y: val }, - } - } - /// Create a new point shifted by `x` in the x-dimension and by `y` in the y-dimension - pub fn shift(&self, p: &Point) -> Point { - Point { - x: p.x + self.x, - y: p.y + self.y, - } - } - /// Create a new point scaled by `p.x` in the x-dimension and by `p.y` in the y-dimension - pub fn scale(&self, p: &Point) -> Point { - Point { - x: p.x * self.x, - y: p.y * self.y, - } - } - /// Get the coordinate associated with direction `dir` - pub fn coord(&self, dir: Dir) -> isize { - match dir { - Dir::Horiz => self.x, - Dir::Vert => self.y, - } - } -} -/// Direction Enumeration -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum Dir { - Horiz, - Vert, -} -impl Dir { - /// Whichever direction we are, return the other one. - pub fn other(self) -> Self { - match self { - Self::Horiz => Self::Vert, - Self::Vert => Self::Horiz, - } - } -} -impl Not for Dir { - type Output = Self; - /// Exclamation Operator returns the opposite direction - fn not(self) -> Self::Output { - Self::other(self) - } -} - /// Instance of another Cell #[derive(Debug, Clone)] pub struct Instance { @@ -677,7 +615,11 @@ impl From for Cell { } } -/// Raw-Layout Implementation +/// # Raw-Layout Implementation +/// +/// The geometric-level layout-definition of a [Cell]. +/// Comprised of geometric [Element]s and instances of other [Cell] [Layout]s. +/// #[derive(Debug, Clone, Default)] pub struct Layout { /// Cell Name @@ -689,6 +631,48 @@ pub struct Layout { /// Text Annotations pub annotations: Vec, } +impl Layout { + /// Flatten a [Layout], particularly its hierarchical instances, to a vector of [Element]s + pub fn flatten(&self) -> LayoutResult> { + // Kick off recursive calls, with the identity-transform applied for the top-level `layout` + let mut elems = Vec::new(); + flatten_helper(self, &Transform::identity(), &mut elems)?; + Ok(elems) + } +} +/// Internal helper and core logic for [Layout::flatten]. +fn flatten_helper( + layout: &Layout, + trans: &Transform, + elems: &mut Vec, +) -> LayoutResult<()> { + // Translate each geometric element + for elem in layout.elems.iter() { + // Clone all other data (layer, net, etc.) + // FIXME: hierarchy flattening of net labels + let mut new_elem = elem.clone(); + // And translate the inner shape by `trans` + new_elem.inner = elem.inner.transform(trans); + elems.push(new_elem); + } + // Note text-valued "annotations" are ignored + + // Visit all of `layout`'s instances, recursively getting their elements + for inst in &layout.insts { + // Get the cell's layout-definition, or fail + let cell = inst.cell.read()?; + let layout = cell.layout.as_ref().unwrap(); + + // Create a new [Transform], cascading the parent's and instance's + let inst_trans = Transform::from_instance(&inst.loc, inst.reflect_vert, inst.angle); + let trans = Transform::cascade(&trans, &inst_trans); + + // And recursively add its elements + flatten_helper(&layout, &trans, elems)?; + } + Ok(()) +} + /// # Text Annotation /// /// Note [layout21::raw::TextElement]s are "layer-less", @@ -704,6 +688,11 @@ pub struct TextElement { pub loc: Point, } /// # Primitive Geometric Element +/// +/// Primary unit of [Layout] definition. +/// Combines a geometric [Shape] with a z-axis [Layer], +/// and optional net connectivity annotation. +/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Element { /// Net Name @@ -715,113 +704,30 @@ pub struct Element { /// Shape pub inner: Shape, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum Shape { - Rect { p0: Point, p1: Point }, - Poly { pts: Vec }, - Path { width: usize, pts: Vec }, -} -impl Shape { - /// Retrieve our "origin", or first [Point] - pub fn point0(&self) -> &Point { - match *self { - Shape::Rect { ref p0, p1: _ } => p0, - Shape::Poly { ref pts } => &pts[0], - Shape::Path { ref pts, .. } => &pts[0], - } - } - /// Calculate our center-point - pub fn center(&self) -> Point { - match *self { - Shape::Rect { ref p0, ref p1 } => Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2), - Shape::Path { ref pts, .. } => { - // Place on the center of the first segment - let p0 = &pts[0]; - let p1 = &pts[1]; - Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) - } - Shape::Poly { .. } => { - unimplemented!("Shape::Poly/Path::center"); - } - } - } - /// Indicate whether this shape is (more or less) horizontal or vertical - /// Primarily used for orienting label-text - pub fn orientation(&self) -> Dir { - match *self { - Shape::Rect { ref p0, ref p1 } => { - if (p1.x - p0.x).abs() < (p1.y - p0.y).abs() { - return Dir::Vert; - } - Dir::Horiz - } - // Polygon and Path elements always horizontal, at least for now - Shape::Poly { .. } | Shape::Path { .. } => Dir::Horiz, - } - } - /// Shift coordinates by the (x,y) values specified in `pt` - pub fn shift(&mut self, pt: &Point) { - match *self { - Shape::Rect { - ref mut p0, - ref mut p1, - } => { - p0.x += pt.x; - p0.y += pt.y; - p1.x += pt.x; - p1.y += pt.y; - } - Shape::Poly { ref mut pts } => { - for p in pts.iter_mut() { - p.x += pt.x; - p.y += pt.y; - } - } - Shape::Path { ref mut pts, .. } => { - for p in pts.iter_mut() { - p.x += pt.x; - p.y += pt.y; - } - } - } - } - /// Boolean indication of whether we contain point `pt` - pub fn contains(&self, pt: &Point) -> bool { - match self { - Shape::Rect { ref p0, ref p1 } => { - p0.x.min(p1.x) <= pt.x - && p0.x.max(p1.x) >= pt.x - && p0.y.min(p1.y) <= pt.y - && p0.y.max(p1.y) >= pt.y - } - Shape::Poly { .. } => false, // FIXME! todo!(), - Shape::Path { ref width, ref pts } => { - // Break into segments, and check for intersection with each - // Probably not the most efficient way to do this, but a start. - // Only "Manhattan paths", i.e. those with segments solely running vertically or horizontally, are supported. - // FIXME: even with this method, there are some small pieces at corners which we'll miss. - // Whether these are relevant in real life, tbd. - let width = isize::try_from(*width).unwrap(); // FIXME: probably store these signed, check them on creation - for k in 0..pts.len() - 1 { - let rect = if pts[k].x == pts[k + 1].x { - Shape::Rect { - p0: Point::new(pts[k].x - width / 2, pts[k].y), - p1: Point::new(pts[k].x + width / 2, pts[k + 1].y), - } - } else if pts[k].y == pts[k + 1].y { - Shape::Rect { - p0: Point::new(pts[k].x, pts[k].y - width / 2), - p1: Point::new(pts[k + 1].x, pts[k].y + width / 2), - } - } else { - unimplemented!("Unsupported Non-Manhattan Path") - }; - if rect.contains(pt) { - return true; - } - } - false - } - } - } + +/// Location, orientation, and angular rotation for an [Instance] +/// Note these fields exist "flat" in [Instance] as well, +/// and are grouped here for convenience. +pub struct InstancePlace { + /// Location of `cell` origin + /// regardless of rotation or reflection + pub loc: Point, + /// Vertical reflection, + /// applied *before* rotation + pub reflect_vert: bool, + /// Angle of rotation (degrees), + /// Clockwise and applied *after* reflection + pub angle: Option, } + +// pub struct Flatten<'l> { +// lib: &'l Library, +// top: &'l Layout, +// } + +// impl Iterator for Flatten { +// type Item = Element; +// fn next(&mut self) -> Option { +// unimplemented!() +// } +// } From d98db8001f9e4569a623e334cf38f48afc79c9f2 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Mon, 17 Jan 2022 10:55:21 -0800 Subject: [PATCH 02/28] Initial `rules` schema --- Cargo.toml | 1 + rules21/Cargo.toml | 28 ++++++++ rules21/readme.md | 0 rules21/readme.tpl | 7 ++ rules21/src/data.rs | 170 ++++++++++++++++++++++++++++++++++++++++++++ rules21/src/lib.rs | 13 ++++ rules21/src/read.rs | 0 7 files changed, 219 insertions(+) create mode 100644 rules21/Cargo.toml create mode 100644 rules21/readme.md create mode 100644 rules21/readme.tpl create mode 100644 rules21/src/data.rs create mode 100644 rules21/src/lib.rs create mode 100644 rules21/src/read.rs diff --git a/Cargo.toml b/Cargo.toml index 23afac4..c0eb015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "rules21", "gds21", "lef21", "layout21protos", diff --git a/rules21/Cargo.toml b/rules21/Cargo.toml new file mode 100644 index 0000000..7fb38c8 --- /dev/null +++ b/rules21/Cargo.toml @@ -0,0 +1,28 @@ +[package] +description = "Layout Design Rules Schema and Parser" +name = "rules21" +# Shared layout21 attributes +authors = ["Dan Fritchman "] +edition = "2018" +exclude = ["resources"] +license = "BSD-3-Clause" +repository = "https://github.com/dan-fritchman/Layout21" +version = "0.2.1" +workspace = "../" + +[dependencies] +# Local workspace dependencies +layout21raw = {path = "../layout21raw", version = "0.2.1"} +layout21utils = {path = "../layout21utils", version = "0.2.1"} + +# External dependencies +# derive_builder = "0.9.0" +# enum_dispatch = "0.3.1" +# num-derive = "0.3" +# num-traits = "0.2" +# rust_decimal = {version = "1.14.3"} +# serde = {version = "1.0", features = ["derive"]} +# serde_derive = "1.0.88" +# derive_more = "0.99.16" +# once_cell = "1.8.0" +# fstrings = "0.2.3" diff --git a/rules21/readme.md b/rules21/readme.md new file mode 100644 index 0000000..e69de29 diff --git a/rules21/readme.tpl b/rules21/readme.tpl new file mode 100644 index 0000000..c70acf8 --- /dev/null +++ b/rules21/readme.tpl @@ -0,0 +1,7 @@ + +# Rules21 + +DRC and LVS rule-definitions + +{{readme}} + diff --git a/rules21/src/data.rs b/rules21/src/data.rs new file mode 100644 index 0000000..ce1916a --- /dev/null +++ b/rules21/src/data.rs @@ -0,0 +1,170 @@ +use std::collections::HashMap; + +// Local Imports +use layout21raw as raw; +use layout21utils as utils; +use utils::ptr::Ptr; + +/// # Rules for Circuit-Extraction from a Layout +/// +/// As typically performed as part of a layout-vs-schematic check, +/// or as the early stages of a parasitic extraction. +/// +pub struct CircuitExtractionRules { + pub layers: Vec, + pub devices: Vec, + pub connect_rules: Vec, +} + +fn extract_instances( + layout: &raw::Layout, + rules: &CircuitExtractionRules, +) -> Result, ()> { + // // Flatten `layout`, replacing all layout-instances with raw shapes + // let flat = flatten(layout); + // // Associate each shape in `layout` with one of `rules` layers, or fail + // map_some_layer_thing(layout_layers, rules.layers); + // let mut instances = Vec::new(); + // // For each device-definition in `rules`, search each shape in `layout` for a match + // for device in rules.devices.iter() { + // for id_layer_shape in shapes[device.id_layer.read()].iter() { + // for port_layer in device.ports.iter() { + // for port_layer_shape in shapes[port_layer.read()].iter() { + // if everything_overlaps { + // instances.push(Instance::new(/* stuff */)); + // } + // } + // } + // } + // } + + todo!() +} + +pub struct PrimaryLayer { + pub name: String, + pub desc: String, + pub id: u64, + pub spec: LayerMapSpec, +} + +pub struct LayerMapSpec { + pub layernum: u32, + pub datatype: u32, +} + +pub enum LayerData { + Primary(PrimaryLayer), + Derived(DerivedLayer), +} +pub struct Layer { + pub data: LayerData, + pub connect_rules: Vec, +} + +/// # Derived Layout Layer +/// +/// Produced by a set of geometric expressions applied to other [Layer]s. +/// +pub struct DerivedLayer { + pub name: String, + pub desc: String, + pub id: u64, + pub expr: GeomOp, +} + +/// # Enumerated Geometric Operators +/// The primary evaluation-mechanism for derived layers +pub enum GeomOp { + BinaryOp(BinaryOp), +} + +/// # Binary Geometric Operation +/// +/// Produces a [DerivedLayer] as a function of two other [Layer]s. +/// +pub struct BinaryOp { + pub op: BinaryOperator, + pub lhs: Ptr, + pub rhs: Ptr, +} + +/// # Enumerated Binary Geometric Operations +pub enum BinaryOperator { + Union, + Intersection, + Difference, + Xor, + /// Selects all shapes from [Layer] `lhs` that touch or are coincident with shapes in [Layer] `rhs`. + Interact, +} + +pub struct Device { + pub name: String, + pub desc: String, + pub id: u64, + /// Key-layer identifying the device + pub id_layer: Ptr, + /// Port Layers + pub ports: Vec, + /// Additional, optional layers, which generally dictate device flavors or parameters + pub optional_layers: Vec>, + /// Set of parameters and evaluation functions + pub params: Vec, + /// Set of port-symmetries, i.e. ports that can be arbitrarily swapped. + pub symmetries: Symmetries, +} + +/// # Enumerated Types of Layout-Extractable [Device] +pub enum DeviceKind { + Mos(MosKind), + Bipolar(BipolarKind), + Diode, + Capacitor, + Resistor, +} + +pub enum MosKind { + Nmos, + Pmos, +} +pub enum BipolarKind { + Npn, + Pnp, +} + +/// # Extracted Device Port +/// +pub struct Port { + pub name: String, + pub layer: Ptr, +} + +pub struct Symmetries; + +pub struct ParamDeclaration { + pub name: String, + pub desc: String, +} + +/// # Extracted Instance of a [Device] +pub struct Instance { + pub name: String, + pub id: u64, + pub device: Ptr, + pub shapes: Vec>, + pub ports: HashMap>, + pub params: HashMap, +} +pub struct Shape; +pub struct ParamValue; + +/// # Connection Rule to a [Layer] +/// +/// Stored as attributes of each [Layer], indicating which [Layer]s are connected to it. +/// Optionally specifies a third `through` [Layer], typically a via, +/// which if specified is required to coincide with *both* `from` and `to` to form a connection. +pub struct ConnectRule { + pub to: Ptr, + pub through: Option>, +} diff --git a/rules21/src/lib.rs b/rules21/src/lib.rs new file mode 100644 index 0000000..d84e1b1 --- /dev/null +++ b/rules21/src/lib.rs @@ -0,0 +1,13 @@ +//! +//! # Rules21 +//! +//! Layout design rules for design-rule correctness (DRC), +//! layout-versus-schematic (LVS), and similar tools. +//! + +pub mod data; +pub use data::*; + +pub mod read; +pub use read::*; + diff --git a/rules21/src/read.rs b/rules21/src/read.rs new file mode 100644 index 0000000..e69de29 From ad737b73cf3523fea3124ae59804b4d747b7bdf3 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 25 Jan 2022 18:28:35 -0800 Subject: [PATCH 03/28] Move `enumstr` macro and associated trait to `layout21utils` --- layout21utils/src/enumstr.rs | 138 +++++++++++++++++++++++++++++++++++ layout21utils/src/lib.rs | 3 + lef21/src/lib.rs | 60 +-------------- lef21/src/read.rs | 2 +- 4 files changed, 144 insertions(+), 59 deletions(-) create mode 100644 layout21utils/src/enumstr.rs diff --git a/layout21utils/src/enumstr.rs b/layout21utils/src/enumstr.rs new file mode 100644 index 0000000..12c6457 --- /dev/null +++ b/layout21utils/src/enumstr.rs @@ -0,0 +1,138 @@ +//! +//! # Enum-String Mapping Module +//! +//! Primarily defines the [enumstr] macro and paired [EnumStr] trait, +//! for defining a mapping between an enum and a string. +//! Widely useful for parsing and writing of many common layout-related formats +//! which expose enumerated values as one of a set of strings. +//! +//! The [EnumStr] trait defines two central methods: +//! * `to_str(&self) -> &'static str` converts the enum to its String values. +//! * `from_str(&str) -> Option` does the opposite, returning an [Option] indicator of success or failure. +//! +//! The [enumstr] macros is invoked with a colon-separated list of variants and their string-values. +//! It produces a struct and its implementation of [EnumStr]. +//! +//! Example: +//! +//! ```rs +//! use layout21utils::enumstr; +//! +//! enumstr!( +//! /// # Light-Switch States: ON and OFF +//! LightSwitch { +//! On: "ON", +//! Off: "OFF", +//! } +//! ); +//! ``` +//! + +/// +/// # String-Enumeration Trait +/// +/// Defines two central methods: +/// * `to_str(&self) -> &'static str` converts the enum to its String values. +/// * `from_str(&str) -> Option` does the opposite, returning an [Option] indicator of success or failure. +/// +/// While [EnumStr] can be implemented by any struct, its primary intent is +/// for implementation by the [enumstr] macro. +/// +pub trait EnumStr: std::marker::Sized { + fn to_str(&self) -> &'static str; + fn from_str(txt: &str) -> Option; +} + +/// +/// # Enum-String Pairing Macro +/// +/// For creating an `enum` which: +/// * (a) Has paired string-values, as commonly arrive in text-format fields. +/// * (b) Automatically implement the [EnumStr] trait for conversions to and from these strings. +/// * (c) Automatically implement [std::fmt::Display] writing the string-values +/// +/// All variants are fieldless, and include derived implementations of common traits notably including `serde::{Serialize,Deserialize}`. +/// +/// Example: +/// +/// ```rs +/// use layout21utils::enumstr; +/// +/// enumstr!( +/// /// # Light-Switch States: ON and OFF +/// LightSwitch { +/// On: "ON", +/// Off: "OFF", +/// } +/// ); +/// ``` +/// +#[macro_export] +macro_rules! enumstr { + ( $(#[$meta: meta])* + $enum_name: ident { + $( $variant: ident : $strval: literal ),* $(,)? + }) => { + $(#[$meta])* + #[allow(dead_code)] + #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] + pub enum $enum_name { + $( #[doc=$strval] + $variant ),* + } + impl EnumStr for $enum_name { + /// Convert a [$enum_name] variant to its paired (static) string value. + #[allow(dead_code)] + fn to_str(&self) -> &'static str { + match self { + $( Self::$variant => $strval),*, + } + } + /// Create a [$enum_name] from one of its string-values. + /// Returns `None` if input `txt` does not match one of [$enum_name]'s variants. + /// Note `from_str` is case *sensitive*, i.e. uses a native string comparison. + /// If case-insensitive matching is intended instead, re-case outside `from_str`. + fn from_str(txt: &str) -> Option { + match txt { + $( $strval => Some(Self::$variant)),*, + _ => None, + } + } + } + impl ::std::fmt::Display for $enum_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + let s = match self { + $( Self::$variant => $strval),*, + }; + write!(f, "{}", s) + } + } + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + #[test] + fn test_enumstr() { + use super::*; + + enumstr!( + /// # Light-Switch States: ON and OFF + LightSwitch { + On: "ON", + Off: "OFF", + } + ); + + // Test conversion to string + assert_eq!(LightSwitch::On.to_str(), "ON"); + assert_eq!(LightSwitch::Off.to_str(), "OFF"); + + // Test conversion from string + assert_eq!(LightSwitch::from_str("ON"), Some(LightSwitch::On)); + assert_eq!(LightSwitch::from_str("OFF"), Some(LightSwitch::Off)); + assert_eq!(LightSwitch::from_str("NEITHER"), None); + } +} diff --git a/layout21utils/src/lib.rs b/layout21utils/src/lib.rs index fd59af9..2d40a2a 100644 --- a/layout21utils/src/lib.rs +++ b/layout21utils/src/lib.rs @@ -13,3 +13,6 @@ pub use error::*; pub mod dep_order; pub use dep_order::*; + +pub mod enumstr; +pub use enumstr::*; diff --git a/lef21/src/lib.rs b/lef21/src/lib.rs index 91b5178..7e5fa03 100644 --- a/lef21/src/lib.rs +++ b/lef21/src/lib.rs @@ -117,7 +117,7 @@ extern crate fstrings; mod read; mod write; use layout21utils as utils; -pub use utils::{SerdeFile, SerializationFormat}; +pub use utils::{enumstr, EnumStr, SerdeFile, SerializationFormat}; // Unit tests #[cfg(test)] @@ -613,62 +613,6 @@ pub struct LefSite { #[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct Unsupported; -/// # Lef String-Enumeration Trait -/// -/// Defines two central methods: -/// * `to_str(&self) -> &'static str` converts the enum to its Lef-String values. -/// * `from_str(&str) -> Option` does the opposite, returning an [Option] indicator of success or failure. -/// -trait LefEnum: std::marker::Sized { - fn to_str(&self) -> &'static str; - fn from_str(txt: &str) -> Option; -} -/// Macro for creating `enum`s which: -/// * (a) Have paired string-values, as commonly arrive in enumerated LEF fields such as "ON" / "OFF", "CLASS", etc, and -/// * (b) Automatically implement the [LefEnum] trait for conversions to and from these strings. -/// * (c) Automatically implement [std::fmt::Display] writing the string-values -/// All variants are fieldless, and include derived implementations of common traits notably including `serde::{Serialize,Deserialize}`. -macro_rules! enumstr { - ( $(#[$meta: meta])* - $enum_name: ident { - $( $variant: ident : $strval: literal ),* $(,)? - }) => { - $(#[$meta])* - #[allow(dead_code)] - #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] - pub enum $enum_name { - $( #[doc=$strval] - $variant ),* - } - impl LefEnum for $enum_name { - /// Convert a [$enum_name] to the (static) string-keyword used in the Lef format - #[allow(dead_code)] - fn to_str(&self) -> &'static str { - match self { - $( Self::$variant => $strval),*, - } - } - /// Create a [$enum_name] from one of the string-values specified in the Lef format. - /// Returns `None` if input `txt` does not match one of [$enum_name]'s variants. - /// Note `from_str` is case *sensitive*, i.e. uses a native string comparison. - /// Conversion to case-insensitive matching generall requires re-casing outside `from_str`. - fn from_str(txt: &str) -> Option { - match txt { - $( $strval => Some(Self::$variant)),*, - _ => None, - } - } - } - impl ::std::fmt::Display for $enum_name { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - let s = match self { - $( Self::$variant => $strval),*, - }; - write!(f, "{}", s) - } - } - } -} enumstr!( /// # Lef Key(Word)s /// @@ -925,7 +869,7 @@ impl From<&str> for LefError { Self::Str(e.into()) } } -// One of these days, this way is gonna way, and we'll delete all these specific error-types above. +// One of these days, this way is gonna WORK, and we'll delete all these specific error-types above. // impl From for LefError { // /// Wrap External Errors // fn from(e: E) -> Self { diff --git a/lef21/src/read.rs b/lef21/src/read.rs index acf79ab..a9a8151 100644 --- a/lef21/src/read.rs +++ b/lef21/src/read.rs @@ -1028,7 +1028,7 @@ impl<'src> LefParser<'src> { Ok(String::from(txt)) } /// Parse an enumerated string-value of type - fn parse_enum(&mut self) -> LefResult { + fn parse_enum(&mut self) -> LefResult { let txt = self.get_name()?; match T::from_str(&txt.to_ascii_uppercase()) { Some(t) => Ok(t), From 40cb6cce45ea33d93f122539844cf274d466a315 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 25 Jan 2022 18:56:47 -0800 Subject: [PATCH 04/28] Break out `lef21::data` module --- layout21utils/src/enumstr.rs | 3 +- lef21/src/data.rs | 783 +++++++++++++++++++++++++++++++++++ lef21/src/lib.rs | 783 +---------------------------------- lef21/src/read.rs | 8 +- lef21/src/write.rs | 9 +- 5 files changed, 806 insertions(+), 780 deletions(-) create mode 100644 lef21/src/data.rs diff --git a/layout21utils/src/enumstr.rs b/layout21utils/src/enumstr.rs index 12c6457..c21245d 100644 --- a/layout21utils/src/enumstr.rs +++ b/layout21utils/src/enumstr.rs @@ -113,11 +113,10 @@ macro_rules! enumstr { #[cfg(test)] pub mod tests { use super::*; + use serde::{Deserialize, Serialize}; #[test] fn test_enumstr() { - use super::*; - enumstr!( /// # Light-Switch States: ON and OFF LightSwitch { diff --git a/lef21/src/data.rs b/lef21/src/data.rs new file mode 100644 index 0000000..f63d683 --- /dev/null +++ b/lef21/src/data.rs @@ -0,0 +1,783 @@ +//! +//! # Lef Data Model +//! +//! + +// Std-Lib +use std::convert::TryFrom; +use std::path::Path; + +// Crates.io Imports +use derive_builder::Builder; +use derive_more::{Add, AddAssign, Sub, SubAssign}; +use once_cell::sync::Lazy; +#[allow(unused_imports)] +use rust_decimal::prelude::*; +use serde::{Deserialize, Serialize}; + +// Layout21 Imports +use crate::utils::{enumstr, EnumStr}; + +/// +/// # LefDecimal +/// +/// Internal type alias for all decimal-valued data. +/// Uses [rust_decimal](https://crates.io/crates/rust_decimal) internally. +/// +pub type LefDecimal = rust_decimal::Decimal; + +// Static short-hands for two common [LefDecimal] values, both representing spec-versions +// Note [`once_cell`](https://docs.rs/once_cell/1.8.0/once_cell/#lazy-initialized-global-data) +// demands these be `static`, not `const`, for reasons outside our grasp. +pub(crate) static V5P4: Lazy = Lazy::new(|| LefDecimal::from_str("5.4").unwrap()); +pub(crate) static V5P8: Lazy = Lazy::new(|| LefDecimal::from_str("5.8").unwrap()); + +/// # Lef Library +/// +/// LEF's primary design-content container, including a set of macro/cell definitions and associated metadata. +#[derive(Default, Clone, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[builder(pattern = "owned", setter(into))] +pub struct LefLibrary { + // Required + /// Macro Definitions + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub macros: Vec, + /// Site Definitions + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub sites: Vec, + + // Optional + /// Lef Spec Version + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub version: Option, + /// Case-Sensitive Name Setting + /// Valid for LEF versions 5.4 and earlier + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub names_case_sensitive: Option, + /// Wire-Extension Pin Settings + /// Valid for LEF versions 5.4 and earlier + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub no_wire_extension_at_pin: Option, + /// Bus-Bit Separator Characters + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub bus_bit_chars: Option<(char, char)>, + /// Divider Character + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub divider_char: Option, + /// Dimensional Units + /// Recommended to be specified in a tech-lef. But turns up in libraries as well. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub units: Option, + + // Unsupported + /// Via Definitions (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub vias: Unsupported, + /// Syntax Extensions (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub extensions: Unsupported, +} +impl LefLibrary { + /// Create a new and initially empty [LefLibrary]. + /// Also available via [Default]. + pub fn new() -> LefLibrary { + LefLibrary::default() + } + /// Open a [LefLibrary] from file `fname` + pub fn open(fname: impl AsRef) -> LefResult { + super::read::parse_file(fname) + } + /// Write a [LefLibrary] to file `fname`. + pub fn save(&self, fname: impl AsRef) -> LefResult<()> { + super::write::save(self, fname) + } + /// Write a [LefLibrary] to a LEF-format [String]. + pub fn to_string(&self) -> LefResult { + super::write::to_string(self) + } +} +/// # Lef Macro +/// +/// The primary block-level construct comprising each [LefLibrary]. +/// Defines a hardware-block's physical abstract, including: +/// * Pin definitions (`pins`) with locations, directions, and associated metadata +/// * Required blockage-obstructions (`obs`) +/// * A variety of other block-level metadata +/// +#[derive(Default, Clone, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[builder(pattern = "owned", setter(into))] +pub struct LefMacro { + // Required + /// Macro Name + pub name: String, + /// Pin List + #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[builder(default)] + pub pins: Vec, + /// Obstructions + #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[builder(default)] + pub obs: Vec, + + // Optional + /// Macro Class + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub class: Option, + /// Foreign (i.e. GDSII, DEF) Cell + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub foreign: Option, + /// X-Y Origin + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub origin: Option, + /// Outline Size + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub size: Option<(LefDecimal, LefDecimal)>, + /// Rotational & Translation Symmetries + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub symmetry: Option>, + /// Site Name + /// Note the optional `SITEPATTERN` is not supported + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub site: Option, + /// Source + /// Valid for LEF versions 5.4 and earlier + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub source: Option, + + // Unsupported + /// Fixed Mask Option (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub fixed_mask: Unsupported, + /// Electrically-Equivalent Cell (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub eeq: Unsupported, + /// Density Objects (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub density: Unsupported, + /// Properties (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub properties: Unsupported, +} +impl LefMacro { + /// Create a new and initially empty [LefMacro] with name `name` + pub fn new(name: impl Into) -> LefMacro { + let name = name.into(); + LefMacro { + name, + ..Default::default() + } + } +} +/// # [LefMacro] Classes +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum LefMacroClass { + Cover { bump: bool }, + Ring, + Block { tp: Option }, + Pad { tp: Option }, + Core { tp: Option }, + EndCap { tp: LefEndCapClassType }, +} +/// # Lef Foreign Cell Declaration +/// +/// Declares the linkage to another cell, commonly in DEF or GDSII format. +/// Foreign-cell references are stored exacty as in the LEF format: as a string cell-name. +/// The optional `ORIENT` feature is not supported. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LefForeign { + /// Foreign Cell Name + pub cell_name: String, + /// Location + pub pt: Option, + + // Unsupported Fields + /// Orientation (Unsupported) + #[serde(default, skip_serializing)] + pub orient: Unsupported, +} +/// # Lef Pin Definition +/// +/// A named, directed pin, including one or more "weakly connected" physical [LefPort]s. +#[derive(Clone, Default, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[builder(pattern = "owned", setter(into))] +pub struct LefPin { + // Required Fields + /// Pin Name + pub name: String, + /// Port Geometries + pub ports: Vec, + + // Optional Fields + /// Direction + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub direction: Option, + /// Usage / Role + #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(rename(serialize = "use", deserialize = "use"))] + #[builder(default, setter(strip_option))] + pub use_: Option, + /// Shape + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub shape: Option, + /// Antenna Model + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub antenna_model: Option, + /// Antenna Attributes + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub antenna_attrs: Vec, + + // Unsupported + /// Taper Rule (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub taper_rule: Unsupported, + /// Net Expression (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub net_expr: Unsupported, + /// Supply Sensitivity (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub supply_sensitivity: Unsupported, + /// Ground Sensitivity (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub ground_sensitivity: Unsupported, + /// Must-Join (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub must_join: Unsupported, + /// Properties (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub properties: Unsupported, +} +/// # Lef Pin Direction +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum LefPinDirection { + Input, + Output { tristate: bool }, + Inout, + FeedThru, +} +impl std::fmt::Display for LefPinDirection { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s = match self { + Self::Input => "INPUT", + Self::Inout => "INOUT", + Self::FeedThru => "FEEDTHRU", + Self::Output { tristate: false } => "OUTPUT", + Self::Output { tristate: true } => "OUTPUT TRISTATE", + }; + write!(f, "{}", s) + } +} +/// # Lef Antenna Attributes +/// +/// Stored as key-value pairs from string-keys named "ANTENNA*" to [LefDecimal] values. +/// Note each pair may have an optional `layer` specifier, +/// and that each key may have multiple attributes, generally specifying different layers. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LefPinAntennaAttr { + pub key: String, + pub val: LefDecimal, + pub layer: Option, +} +/// # Lef Port +/// +/// Defines the physical locations and optional metadata of a port on a pin. +/// LEF includes the notion of multiple "weakly connected" ports per pin; +/// each [LefPort] is one such weakly-connected point. +#[derive(Clone, Default, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[builder(pattern = "owned", setter(into))] +pub struct LefPort { + /// Port-Class + #[serde(default, skip_serializing_if = "Option::is_none")] + pub class: Option, + /// Layers & Geometries + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub layers: Vec, +} +/// # Lef Single-Layer Geometry Store +/// +/// Most LEF spatial data (e.g. ports, blockages) is organized by layer. +/// [LefLayerGeometries] stores the combination of a layer (name) +/// and suite of geometric primitives (e.g. rectangles, polygons) and vias on that layer. +/// +/// [LefLayerGeometries] are the primary building block of [LefPort]s and macro obstructions. +/// +#[derive(Clone, Default, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[builder(pattern = "owned", setter(into))] +pub struct LefLayerGeometries { + // Required + /// Layer Name + pub layer_name: String, + /// Geometries + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub geometries: Vec, + /// Vias + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub vias: Vec, + + // Optional + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub except_pg_net: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub spacing: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub width: Option, +} +/// # Lef Via Instance +/// +/// A located instance of via-type `via_name`, typically used as part of a [LefLayerGeometries] definition. +/// The via-type is generally interpreted as a string-valued reference into tech-lef data. +/// It is stored in each [LefVia] exactly as in LEF, as a string type-name. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LefVia { + /// Via-Type Name + pub via_name: String, + /// Location + pub pt: LefPoint, +} +/// # Enumerated Layer-Spacing Options +/// Includes absolute spacing and design-rule-width modifiers. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum LefLayerSpacing { + Spacing(LefDecimal), + DesignRuleWidth(LefDecimal), +} +/// # Lef Geometric Object Enumeration +/// Includes [LefShape]s and Iterators thereof +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum LefGeometry { + /// Single Shape + Shape(LefShape), + /// Repeated Iteration/ Array of Shapes (Unsupported) + Iterate { + shape: LefShape, + pattern: Unsupported, + }, +} +/// # Lef Shape Enumeration +/// Includes each of LEF's individual geometric primitives: +/// rectangles, polygons, and paths. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum LefShape { + Rect(LefPoint, LefPoint), + Polygon(Vec), + Path(Vec), +} +/// # Lef X-Y Spatial Point +/// +/// Specified in [LefDecimal]-valued Cartesian coordinates. +/// Supports common mathematical operations (Add, Sub, increment, etc.). +#[derive( + Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq, Add, AddAssign, Sub, SubAssign, +)] +pub struct LefPoint { + pub x: LefDecimal, + pub y: LefDecimal, +} +impl LefPoint { + /// Create a new [LefPoint] + pub fn new(x: impl Into, y: impl Into) -> Self { + Self { + x: x.into(), + y: y.into(), + } + } +} +impl std::fmt::Display for LefPoint { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} {}", self.x, self.y) + } +} +/// # Lef Distance Units per Micron +/// +/// A constrained numeric type. Allowed values of [LefDbuPerMicron] are: +/// [100, 200, 400, 800, 1000, 2000, 4000, 8000, 10_000, 20_000]. +/// Adherence to this set is checked at construction time. +/// +#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LefDbuPerMicron(pub u32); +impl LefDbuPerMicron { + /// Create a new [LefDbuPerMicron], checking internally required conditions + pub fn try_new(x: LefDecimal) -> LefResult { + // Check that this is an integer, no fractional part + if !x.fract().is_zero() { + return Err("DBU per Micron must be an integer".into()); + } + // Check the for allowed values + if ![100, 200, 400, 800, 1000, 2000, 4000, 8000, 10_000, 20_000].contains(&x.mantissa()) { + return Err("Invalid DBU per Micron value".into()); + } + // Convert to u32. Note the `unwrap` here is safe, + // as we have already verified `mantissa` is in the list above, + // all of which fit in a u32. + let val = u32::try_from(x.mantissa()).unwrap(); + Ok(Self(val)) + } + /// Return `self`'s value as an integer. + pub fn value(&self) -> u32 { + self.0 + } +} +/// # Lef Physical-Dimension Units +/// +/// Conversion factors for a variety of physical quantities. +/// Only the distance-measurement `database_microns` is supported. +/// +#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LefUnits { + /// Database Distance Units per Micron + /// Defaults to 100, i.e. 1 DBU = 10nm + #[serde(default, skip_serializing_if = "Option::is_none")] + pub database_microns: Option, + + // Unsupported Fields + #[serde(default, skip_serializing)] + pub time_ns: Unsupported, + #[serde(default, skip_serializing)] + pub capacitance_pf: Unsupported, + #[serde(default, skip_serializing)] + pub resistance_ohms: Unsupported, + #[serde(default, skip_serializing)] + pub power_mw: Unsupported, + #[serde(default, skip_serializing)] + pub current_ma: Unsupported, + #[serde(default, skip_serializing)] + pub voltage_volts: Unsupported, + #[serde(default, skip_serializing)] + pub frequency_mhz: Unsupported, +} +/// # Lef Site Definition +/// +/// Defines a placement-site in designs. +/// Dictates the placement grid for a family of macros. +/// +#[derive(Clone, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[builder(pattern = "owned", setter(into))] +pub struct LefSite { + // Required + /// Site Name + pub name: String, + /// Site Class + pub class: LefSiteClass, + /// Size + pub size: (LefDecimal, LefDecimal), + + // Optional + /// Rotational & Translation Symmetries + #[serde(default, skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub symmetry: Option>, + + // Unsupported + /// Row Patterns, re other previously defined sites (Unsupported) + #[serde(default, skip_serializing)] + #[builder(default)] + pub row_pattern: Unsupported, +} +/// # Unsupported Feature +/// +/// Empty placeholder struct for unsupported LEF features. +/// These fields are largely included for documentation purposes. +/// They are never parsed, never written or serialized, and can only be set to the zero-size [Unsupported] value. +#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct Unsupported; + +enumstr!( + /// # Lef Key(Word)s + /// + /// Enumerated "key(word)s" used in LEF parsing and generation. + /// + /// Unlike typical programming languages, LEF does not really have *keywords* in the sense of being reserved at all points in the program. + /// Legality of [LefKey]s is instead context-dependent, more as in a YAML or JSON schema. + /// For example a [LefMacro] is free to use the name "MACRO" for one of its pins, whereas while during [LefLibrary] definition, "MACRO" is a key with special meaning. + /// + /// LEF syntax is case-insensitive. [LefKey]s are always written in the (conventional) upper-case form, but are parsed case-insensitively. + /// + LefKey { + Library: "LIBRARY", + Version: "VERSION", + Foreign: "FOREIGN", + Origin: "ORIGIN", + Source: "SOURCE", + NamesCaseSensitive: "NAMESCASESENSITIVE", + NoWireExtensionAtPin: "NOWIREEXTENSIONATPIN", + Macro: "MACRO", + End: "END", + Pin: "PIN", + Port: "PORT", + Obs: "OBS", + Layer: "LAYER", + Direction: "DIRECTION", + Use: "USE", + Shape: "SHAPE", + Path: "PATH", + Polygon: "POLYGON", + Rect: "RECT", + Via: "VIA", + Width: "WIDTH", + Class: "CLASS", + Symmetry: "SYMMETRY", + RowPattern: "ROWPATTERN", + Site: "SITE", + Size: "SIZE", + By: "BY", + BusBitChars: "BUSBITCHARS", + DividerChar: "DIVIDERCHAR", + Units: "UNITS", + BeginExtension: "BEGINEXT", + Tristate: "TRISTATE", + Input: "INPUT", + Output: "OUTPUT", + Inout: "INOUT", + FeedThru: "FEEDTHRU", + ExceptPgNet: "EXCEPTPGNET", + DesignRuleWidth: "DESIGNRULEWIDTH", + Spacing: "SPACING", + Bump: "BUMP", + + // ANTENNA Fields + AntennaModel: "ANTENNAMODEL", + AntennaDiffArea: "ANTENNADIFFAREA", + AntennaGateArea: "ANTENNAGATEAREA", + AntennaPartialMetalArea: "ANTENNAPARTIALMETALAREA", + AntennaPartialMetalSideArea: "ANTENNAPARTIALMETALSIDEAREA", + AntennaPartialCutArea: "ANTENNAPARTIALCUTAREA", + AntennaPartialDiffArea: "ANTENNAPARTIALDIFFAREA", + AntennaMaxAreaCar: "ANTENNAMAXAREACAR", + AntennaMaxSideAreaCar: "ANTENNAMAXSIDEAREACAR", + AntennaMaxCutCar: "ANTENNAMAXCUTCAR", + + // Unsupported + TaperRule: "TAPERRULE", + NetExpr: "NETEXPR", + SupplySensitivity: "SUPPLYSENSITIVITY", + GroundSensitivity: "GROUNDSENSITIVITY", + MustJoin: "MUSTJOIN", + Property: "PROPERTY", + } +); +impl LefKey { + /// Lef Key parsing, performed case-insensitively by internally converting to upper-case. + pub fn parse(txt: &str) -> Option { + Self::from_str(&txt.to_ascii_uppercase()) + } +} +enumstr!( + /// Binary On/Off Settings, Denoted by `ON` and `OFF` + LefOnOff { + On: "ON", + Off: "OFF", + } +); +enumstr!( + /// # Lef/ Def `SOURCE` + /// + /// Specifies the source of a component + /// In all versions since at least 5.7 (2009), SOURCE is a DEF-only field on COMPONENT definitions. + /// Prior versions also include this as a field for LEF MACRO definitions. + LefDefSource { + Netlist: "NETLIST", + Dist: "DIST", + Timing: "TIMING", + User: "USER", + } +); +enumstr!( + /// Specifies which MACRO orientations are valid for placement + LefSymmetry { + X: "X", + Y: "Y", + R90: "R90" + } +); +enumstr!( + /// # Lef Pin-Usage + /// + /// Specifies the usage-intent for a [LefPin]. + /// Note this is the noun form of "use", pronounced with the hard "s" - + /// not the verb form pronounced like the New Jersey second-person plural "yous". + LefPinUse { + Signal: "SIGNAL", + Analog: "ANALOG", + Power: "POWER", + Ground: "GROUND", + Clock: "CLOCK", + } +); +enumstr!( + /// Specifies a pin with special connection requirements because of its shape + LefPinShape { + Abutment: "ABUTMENT", + Ring: "RING", + FeedThru: "FEEDTHRU", + } +); +enumstr!( + /// Identifiers for the enumerated [LefMacroClass]es + LefMacroClassName { + Block: "BLOCK", + Pad: "PAD", + Core: "CORE", + EndCap: "ENDCAP", + Cover: "COVER", + Ring: "RING" + } +); +enumstr!( + /// Sub-Types for Macros of Class [LefMacroClass::Pad] + LefPadClassType { + Input: "INPUT", + Output: "OUTPUT", + Inout: "INOUT", + Power: "POWER", + Spacer: "SPACER", + AreaIo: "AREAIO", + } +); +enumstr!( + /// Sub-Types for Macros of Class [LefMacroClass::EndCap] + LefEndCapClassType { + Pre: "PRE", + Post: "POST", + TopLeft: "TOPLEFT", + TopRight: "TOPRIGHT", + BottomLeft: "BOTTOMLEFT", + BottomRight: "BOTTOMRIGHT", + } +); +enumstr!( + /// Sub-Types for Macros of Class [LefMacroClass::Block] + LefBlockClassType { + BlackBox: "BLACKBOX", + Soft: "SOFT" + } +); +enumstr!( + /// Sub-Types for Macros of Class [LefMacroClass::Core] + LefCoreClassType { + FeedThru: "FEEDTHRU", + TieHigh: "TIEHIGH", + TieLow: "TIELOW", + Spacer: "SPACER", + AntennaCell: "ANTENNACELL", + WellTap: "WELLTAP", + } +); +enumstr!( + /// Sub-Types for Macros of Class [LefMacroClass::Core] + LefPortClass { + None: "NONE", + Core: "CORE", + Bump: "BUMP", + } +); +enumstr!( + /// [LefSite] Classes + LefSiteClass { + Pad: "PAD", + Core: "CORE", + } +); +enumstr!( + /// Antenna Models + LefAntennaModel { + Oxide1: "OXIDE1", + Oxide2: "OXIDE2", + Oxide3: "OXIDE3", + Oxide4: "OXIDE4", + } +); + +/// # Lef Error Enumeration +#[derive(Debug)] +pub enum LefError { + /// Lexer Errors + Lex { + next_char: Option, + line: usize, + pos: usize, + }, + /// Parser Errors + Parse { + tp: super::read::LefParseErrorType, + ctx: Vec, + token: String, + line_content: String, + line_num: usize, + pos: usize, + }, + /// Wrapped errors, generally from other crates + Boxed(Box), + /// String message-valued errors + Str(String), +} +impl From for LefError { + fn from(e: crate::utils::ser::Error) -> Self { + Self::Boxed(Box::new(e)) + } +} +impl From for LefError { + fn from(e: std::io::Error) -> Self { + Self::Boxed(Box::new(e)) + } +} +impl From for LefError { + fn from(e: rust_decimal::Error) -> Self { + Self::Boxed(Box::new(e)) + } +} +impl From for LefError { + /// Convert string-based errors by wrapping them + fn from(e: String) -> Self { + Self::Str(e) + } +} +impl From<&str> for LefError { + /// Convert string-based errors by wrapping them + fn from(e: &str) -> Self { + Self::Str(e.into()) + } +} +// One of these days, this way is gonna WORK, and we'll delete all these specific error-types above. +// impl From for LefError { +// /// Wrap External Errors +// fn from(e: E) -> Self { +// Self::Boxed(Box::new(e)) +// } +// } + +/// Lef21 Library-Wide Result Type +pub type LefResult = Result; + +// Implement the serialization to/from file trait for libraries and macros +impl crate::utils::SerdeFile for LefLibrary {} +impl crate::utils::SerdeFile for LefMacro {} diff --git a/lef21/src/lib.rs b/lef21/src/lib.rs index 7e5fa03..70ef378 100644 --- a/lef21/src/lib.rs +++ b/lef21/src/lib.rs @@ -98,788 +98,21 @@ //! Their examples serve as the basis for Lef21. //! -// Std-Lib -use std::convert::TryFrom; -use std::path::Path; - -// Crates.io Imports -use derive_more::{Add, AddAssign, Sub, SubAssign}; -use once_cell::sync::Lazy; -#[allow(unused_imports)] -use rust_decimal::prelude::*; -use serde::{Deserialize, Serialize}; -#[macro_use] -extern crate derive_builder; +// Crates.io imports, included here for macros #[macro_use] extern crate fstrings; +// Layout21 imports & re-exports +pub(crate) use layout21utils as utils; +pub use utils::{SerdeFile, SerializationFormat}; + // Local modules & re-exports +mod data; +#[doc(inline)] +pub use data::*; mod read; mod write; -use layout21utils as utils; -pub use utils::{enumstr, EnumStr, SerdeFile, SerializationFormat}; // Unit tests #[cfg(test)] mod tests; - -/// # LefDecimal -/// Internal type alias for all decimal-valued data. -/// Uses [rust_decimal](https://crates.io/crates/rust_decimal) internally. -pub type LefDecimal = rust_decimal::Decimal; - -// Static short-hands for two common [LefDecimal] values, both representing spec-versions -// Note [`once_cell`](https://docs.rs/once_cell/1.8.0/once_cell/#lazy-initialized-global-data) -// demands these be `static`, not `const`, for reasons outside our grasp. -static V5P4: Lazy = Lazy::new(|| LefDecimal::from_str("5.4").unwrap()); -static V5P8: Lazy = Lazy::new(|| LefDecimal::from_str("5.8").unwrap()); - -/// # Lef Library -/// -/// LEF's primary design-content container, including a set of macro/cell definitions and associated metadata. -#[derive(Default, Clone, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[builder(pattern = "owned", setter(into), private)] -pub struct LefLibrary { - // Required - /// Macro Definitions - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub macros: Vec, - /// Site Definitions - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub sites: Vec, - - // Optional - /// Lef Spec Version - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub version: Option, - /// Case-Sensitive Name Setting - /// Valid for LEF versions 5.4 and earlier - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub names_case_sensitive: Option, - /// Wire-Extension Pin Settings - /// Valid for LEF versions 5.4 and earlier - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub no_wire_extension_at_pin: Option, - /// Bus-Bit Separator Characters - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub bus_bit_chars: Option<(char, char)>, - /// Divider Character - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub divider_char: Option, - /// Dimensional Units - /// Recommended to be specified in a tech-lef. But turns up in libraries as well. - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub units: Option, - - // Unsupported - /// Via Definitions (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub vias: Unsupported, - /// Syntax Extensions (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub extensions: Unsupported, -} -impl LefLibrary { - /// Create a new and initially empty [LefLibrary]. - /// Also available via [Default]. - pub fn new() -> LefLibrary { - LefLibrary::default() - } - /// Open a [LefLibrary] from file `fname` - pub fn open(fname: impl AsRef) -> LefResult { - read::parse_file(fname) - } - /// Write a [LefLibrary] to file `fname`. - pub fn save(&self, fname: impl AsRef) -> LefResult<()> { - write::save(self, fname) - } - /// Write a [LefLibrary] to a LEF-format [String]. - pub fn to_string(&self) -> LefResult { - write::to_string(self) - } -} -/// # Lef Macro -/// -/// The primary block-level construct comprising each [LefLibrary]. -/// Defines a hardware-block's physical abstract, including: -/// * Pin definitions (`pins`) with locations, directions, and associated metadata -/// * Required blockage-obstructions (`obs`) -/// * A variety of other block-level metadata -/// -#[derive(Default, Clone, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[builder(pattern = "owned", setter(into), private)] -pub struct LefMacro { - // Required - /// Macro Name - pub name: String, - /// Pin List - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[builder(default)] - pub pins: Vec, - /// Obstructions - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[builder(default)] - pub obs: Vec, - - // Optional - /// Macro Class - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub class: Option, - /// Foreign (i.e. GDSII, DEF) Cell - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub foreign: Option, - /// X-Y Origin - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub origin: Option, - /// Outline Size - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub size: Option<(LefDecimal, LefDecimal)>, - /// Rotational & Translation Symmetries - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub symmetry: Option>, - /// Site Name - /// Note the optional `SITEPATTERN` is not supported - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub site: Option, - /// Source - /// Valid for LEF versions 5.4 and earlier - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub source: Option, - - // Unsupported - /// Fixed Mask Option (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub fixed_mask: Unsupported, - /// Electrically-Equivalent Cell (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub eeq: Unsupported, - /// Density Objects (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub density: Unsupported, - /// Properties (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub properties: Unsupported, -} -impl LefMacro { - /// Create a new and initially empty [LefMacro] with name `name` - pub fn new(name: impl Into) -> LefMacro { - let name = name.into(); - LefMacro { - name, - ..Default::default() - } - } -} -/// # [LefMacro] Classes -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub enum LefMacroClass { - Cover { bump: bool }, - Ring, - Block { tp: Option }, - Pad { tp: Option }, - Core { tp: Option }, - EndCap { tp: LefEndCapClassType }, -} -/// # Lef Foreign Cell Declaration -/// -/// Declares the linkage to another cell, commonly in DEF or GDSII format. -/// Foreign-cell references are stored exacty as in the LEF format: as a string cell-name. -/// The optional `ORIENT` feature is not supported. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub struct LefForeign { - /// Foreign Cell Name - pub cell_name: String, - /// Location - pub pt: Option, - - // Unsupported Fields - /// Orientation (Unsupported) - #[serde(default, skip_serializing)] - pub orient: Unsupported, -} -/// # Lef Pin Definition -/// -/// A named, directed pin, including one or more "weakly connected" physical [LefPort]s. -#[derive(Clone, Default, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[builder(pattern = "owned", setter(into), private)] -pub struct LefPin { - // Required Fields - /// Pin Name - pub name: String, - /// Port Geometries - pub ports: Vec, - - // Optional Fields - /// Direction - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub direction: Option, - /// Usage / Role - #[serde(default, skip_serializing_if = "Option::is_none")] - #[serde(rename(serialize = "use", deserialize = "use"))] - #[builder(default, setter(strip_option))] - pub use_: Option, - /// Shape - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub shape: Option, - /// Antenna Model - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub antenna_model: Option, - /// Antenna Attributes - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub antenna_attrs: Vec, - - // Unsupported - /// Taper Rule (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub taper_rule: Unsupported, - /// Net Expression (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub net_expr: Unsupported, - /// Supply Sensitivity (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub supply_sensitivity: Unsupported, - /// Ground Sensitivity (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub ground_sensitivity: Unsupported, - /// Must-Join (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub must_join: Unsupported, - /// Properties (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub properties: Unsupported, -} -/// # Lef Pin Direction -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub enum LefPinDirection { - Input, - Output { tristate: bool }, - Inout, - FeedThru, -} -impl std::fmt::Display for LefPinDirection { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let s = match self { - Self::Input => "INPUT", - Self::Inout => "INOUT", - Self::FeedThru => "FEEDTHRU", - Self::Output { tristate: false } => "OUTPUT", - Self::Output { tristate: true } => "OUTPUT TRISTATE", - }; - write!(f, "{}", s) - } -} -/// # Lef Antenna Attributes -/// -/// Stored as key-value pairs from string-keys named "ANTENNA*" to [LefDecimal] values. -/// Note each pair may have an optional `layer` specifier, -/// and that each key may have multiple attributes, generally specifying different layers. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub struct LefPinAntennaAttr { - key: String, - val: LefDecimal, - layer: Option, -} -/// # Lef Port -/// -/// Defines the physical locations and optional metadata of a port on a pin. -/// LEF includes the notion of multiple "weakly connected" ports per pin; -/// each [LefPort] is one such weakly-connected point. -#[derive(Clone, Default, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[builder(pattern = "owned", setter(into), private)] -pub struct LefPort { - /// Port-Class - #[serde(default, skip_serializing_if = "Option::is_none")] - pub class: Option, - /// Layers & Geometries - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub layers: Vec, -} -/// # Lef Single-Layer Geometry Store -/// -/// Most LEF spatial data (e.g. ports, blockages) is organized by layer. -/// [LefLayerGeometries] stores the combination of a layer (name) -/// and suite of geometric primitives (e.g. rectangles, polygons) and vias on that layer. -/// -/// [LefLayerGeometries] are the primary building block of [LefPort]s and macro obstructions. -/// -#[derive(Clone, Default, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[builder(pattern = "owned", setter(into), private)] -pub struct LefLayerGeometries { - // Required - /// Layer Name - pub layer_name: String, - /// Geometries - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub geometries: Vec, - /// Vias - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub vias: Vec, - - // Optional - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub except_pg_net: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub spacing: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub width: Option, -} -/// # Lef Via Instance -/// -/// A located instance of via-type `via_name`, typically used as part of a [LefLayerGeometries] definition. -/// The via-type is generally interpreted as a string-valued reference into tech-lef data. -/// It is stored in each [LefVia] exactly as in LEF, as a string type-name. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub struct LefVia { - /// Via-Type Name - pub via_name: String, - /// Location - pub pt: LefPoint, -} -/// # Enumerated Layer-Spacing Options -/// Includes absolute spacing and design-rule-width modifiers. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub enum LefLayerSpacing { - Spacing(LefDecimal), - DesignRuleWidth(LefDecimal), -} -/// # Lef Geometric Object Enumeration -/// Includes [LefShape]s and Iterators thereof -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub enum LefGeometry { - /// Single Shape - Shape(LefShape), - /// Repeated Iteration/ Array of Shapes (Unsupported) - Iterate { - shape: LefShape, - pattern: Unsupported, - }, -} -/// # Lef Shape Enumeration -/// Includes each of LEF's individual geometric primitives: -/// rectangles, polygons, and paths. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub enum LefShape { - Rect(LefPoint, LefPoint), - Polygon(Vec), - Path(Vec), -} -/// # Lef X-Y Spatial Point -/// -/// Specified in [LefDecimal]-valued Cartesian coordinates. -/// Supports common mathematical operations (Add, Sub, increment, etc.). -#[derive( - Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq, Add, AddAssign, Sub, SubAssign, -)] -pub struct LefPoint { - pub x: LefDecimal, - pub y: LefDecimal, -} -impl LefPoint { - /// Create a new [LefPoint] - pub fn new(x: impl Into, y: impl Into) -> Self { - Self { - x: x.into(), - y: y.into(), - } - } -} -impl std::fmt::Display for LefPoint { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{} {}", self.x, self.y) - } -} -/// # Lef Distance Units per Micron -/// -/// A constrained numeric type. Allowed values of [LefDbuPerMicron] are: -/// [100, 200, 400, 800, 1000, 2000, 4000, 8000, 10_000, 20_000]. -/// Adherence to this set is checked at construction time. -/// -#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub struct LefDbuPerMicron(u32); -impl LefDbuPerMicron { - /// Create a new [LefDbuPerMicron], checking internally required conditions - pub fn try_new(x: LefDecimal) -> LefResult { - // Check that this is an integer, no fractional part - if !x.fract().is_zero() { - return Err("DBU per Micron must be an integer".into()); - } - // Check the for allowed values - if ![100, 200, 400, 800, 1000, 2000, 4000, 8000, 10_000, 20_000].contains(&x.mantissa()) { - return Err("Invalid DBU per Micron value".into()); - } - // Convert to u32. Note the `unwrap` here is safe, - // as we have already verified `mantissa` is in the list above, - // all of which fit in a u32. - let val = u32::try_from(x.mantissa()).unwrap(); - Ok(Self(val)) - } - /// Return `self`'s value as an integer. - pub fn value(&self) -> u32 { - self.0 - } -} -/// # Lef Physical-Dimension Units -/// -/// Conversion factors for a variety of physical quantities. -/// Only the distance-measurement `database_microns` is supported. -/// -#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub struct LefUnits { - /// Database Distance Units per Micron - /// Defaults to 100, i.e. 1 DBU = 10nm - #[serde(default, skip_serializing_if = "Option::is_none")] - pub database_microns: Option, - - // Unsupported Fields - #[serde(default, skip_serializing)] - pub time_ns: Unsupported, - #[serde(default, skip_serializing)] - pub capacitance_pf: Unsupported, - #[serde(default, skip_serializing)] - pub resistance_ohms: Unsupported, - #[serde(default, skip_serializing)] - pub power_mw: Unsupported, - #[serde(default, skip_serializing)] - pub current_ma: Unsupported, - #[serde(default, skip_serializing)] - pub voltage_volts: Unsupported, - #[serde(default, skip_serializing)] - pub frequency_mhz: Unsupported, -} -/// # Lef Site Definition -/// -/// Defines a placement-site in designs. -/// Dictates the placement grid for a family of macros. -/// -#[derive(Clone, Builder, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[builder(pattern = "owned", setter(into), private)] -pub struct LefSite { - // Required - /// Site Name - pub name: String, - /// Site Class - pub class: LefSiteClass, - /// Size - pub size: (LefDecimal, LefDecimal), - - // Optional - /// Rotational & Translation Symmetries - #[serde(default, skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub symmetry: Option>, - - // Unsupported - /// Row Patterns, re other previously defined sites (Unsupported) - #[serde(default, skip_serializing)] - #[builder(default)] - pub row_pattern: Unsupported, -} -/// # Unsupported Feature -/// -/// Empty placeholder struct for unsupported LEF features. -/// These fields are largely included for documentation purposes. -/// They are never parsed, never written or serialized, and can only be set to the zero-size [Unsupported] value. -#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub struct Unsupported; - -enumstr!( - /// # Lef Key(Word)s - /// - /// Enumerated "key(word)s" used in LEF parsing and generation. - /// - /// Unlike typical programming languages, LEF does not really have *keywords* in the sense of being reserved at all points in the program. - /// Legality of [LefKey]s is instead context-dependent, more as in a YAML or JSON schema. - /// For example a [LefMacro] is free to use the name "MACRO" for one of its pins, whereas while during [LefLibrary] definition, "MACRO" is a key with special meaning. - /// - /// LEF syntax is case-insensitive. [LefKey]s are always written in the (conventional) upper-case form, but are parsed case-insensitively. - /// - LefKey { - Library: "LIBRARY", - Version: "VERSION", - Foreign: "FOREIGN", - Origin: "ORIGIN", - Source: "SOURCE", - NamesCaseSensitive: "NAMESCASESENSITIVE", - NoWireExtensionAtPin: "NOWIREEXTENSIONATPIN", - Macro: "MACRO", - End: "END", - Pin: "PIN", - Port: "PORT", - Obs: "OBS", - Layer: "LAYER", - Direction: "DIRECTION", - Use: "USE", - Shape: "SHAPE", - Path: "PATH", - Polygon: "POLYGON", - Rect: "RECT", - Via: "VIA", - Width: "WIDTH", - Class: "CLASS", - Symmetry: "SYMMETRY", - RowPattern: "ROWPATTERN", - Site: "SITE", - Size: "SIZE", - By: "BY", - BusBitChars: "BUSBITCHARS", - DividerChar: "DIVIDERCHAR", - Units: "UNITS", - BeginExtension: "BEGINEXT", - Tristate: "TRISTATE", - Input: "INPUT", - Output: "OUTPUT", - Inout: "INOUT", - FeedThru: "FEEDTHRU", - ExceptPgNet: "EXCEPTPGNET", - DesignRuleWidth: "DESIGNRULEWIDTH", - Spacing: "SPACING", - Bump: "BUMP", - - // ANTENNA Fields - AntennaModel: "ANTENNAMODEL", - AntennaDiffArea: "ANTENNADIFFAREA", - AntennaGateArea: "ANTENNAGATEAREA", - AntennaPartialMetalArea: "ANTENNAPARTIALMETALAREA", - AntennaPartialMetalSideArea: "ANTENNAPARTIALMETALSIDEAREA", - AntennaPartialCutArea: "ANTENNAPARTIALCUTAREA", - AntennaPartialDiffArea: "ANTENNAPARTIALDIFFAREA", - AntennaMaxAreaCar: "ANTENNAMAXAREACAR", - AntennaMaxSideAreaCar: "ANTENNAMAXSIDEAREACAR", - AntennaMaxCutCar: "ANTENNAMAXCUTCAR", - - // Unsupported - TaperRule: "TAPERRULE", - NetExpr: "NETEXPR", - SupplySensitivity: "SUPPLYSENSITIVITY", - GroundSensitivity: "GROUNDSENSITIVITY", - MustJoin: "MUSTJOIN", - Property: "PROPERTY", - } -); -impl LefKey { - /// Lef Key parsing, performed case-insensitively by internally converting to upper-case. - fn parse(txt: &str) -> Option { - Self::from_str(&txt.to_ascii_uppercase()) - } -} -enumstr!( - /// Binary On/Off Settings, Denoted by `ON` and `OFF` - LefOnOff { - On: "ON", - Off: "OFF", - } -); -enumstr!( - /// # Lef/ Def `SOURCE` - /// - /// Specifies the source of a component - /// In all versions since at least 5.7 (2009), SOURCE is a DEF-only field on COMPONENT definitions. - /// Prior versions also include this as a field for LEF MACRO definitions. - LefDefSource { - Netlist: "NETLIST", - Dist: "DIST", - Timing: "TIMING", - User: "USER", - } -); -enumstr!( - /// Specifies which MACRO orientations are valid for placement - LefSymmetry { - X: "X", - Y: "Y", - R90: "R90" - } -); -enumstr!( - /// # Lef Pin-Usage - /// - /// Specifies the usage-intent for a [LefPin]. - /// Note this is the noun form of "use", pronounced with the hard "s" - - /// not the verb form pronounced like the New Jersey second-person plural "yous". - LefPinUse { - Signal: "SIGNAL", - Analog: "ANALOG", - Power: "POWER", - Ground: "GROUND", - Clock: "CLOCK", - } -); -enumstr!( - /// Specifies a pin with special connection requirements because of its shape - LefPinShape { - Abutment: "ABUTMENT", - Ring: "RING", - FeedThru: "FEEDTHRU", - } -); -enumstr!( - /// Identifiers for the enumerated [LefMacroClass]es - LefMacroClassName { - Block: "BLOCK", - Pad: "PAD", - Core: "CORE", - EndCap: "ENDCAP", - Cover: "COVER", - Ring: "RING" - } -); -enumstr!( - /// Sub-Types for Macros of Class [LefMacroClass::Pad] - LefPadClassType { - Input: "INPUT", - Output: "OUTPUT", - Inout: "INOUT", - Power: "POWER", - Spacer: "SPACER", - AreaIo: "AREAIO", - } -); -enumstr!( - /// Sub-Types for Macros of Class [LefMacroClass::EndCap] - LefEndCapClassType { - Pre: "PRE", - Post: "POST", - TopLeft: "TOPLEFT", - TopRight: "TOPRIGHT", - BottomLeft: "BOTTOMLEFT", - BottomRight: "BOTTOMRIGHT", - } -); -enumstr!( - /// Sub-Types for Macros of Class [LefMacroClass::Block] - LefBlockClassType { - BlackBox: "BLACKBOX", - Soft: "SOFT" - } -); -enumstr!( - /// Sub-Types for Macros of Class [LefMacroClass::Core] - LefCoreClassType { - FeedThru: "FEEDTHRU", - TieHigh: "TIEHIGH", - TieLow: "TIELOW", - Spacer: "SPACER", - AntennaCell: "ANTENNACELL", - WellTap: "WELLTAP", - } -); -enumstr!( - /// Sub-Types for Macros of Class [LefMacroClass::Core] - LefPortClass { - None: "NONE", - Core: "CORE", - Bump: "BUMP", - } -); -enumstr!( - /// [LefSite] Classes - LefSiteClass { - Pad: "PAD", - Core: "CORE", - } -); -enumstr!( - /// Antenna Models - LefAntennaModel { - Oxide1: "OXIDE1", - Oxide2: "OXIDE2", - Oxide3: "OXIDE3", - Oxide4: "OXIDE4", - } -); - -/// # Lef Error Enumeration -#[derive(Debug)] -pub enum LefError { - /// Lexer Errors - Lex { - next_char: Option, - line: usize, - pos: usize, - }, - /// Parser Errors - Parse { - tp: read::LefParseErrorType, - ctx: Vec, - token: String, - line_content: String, - line_num: usize, - pos: usize, - }, - /// Wrapped errors, generally from other crates - Boxed(Box), - /// String message-valued errors - Str(String), -} -impl From for LefError { - fn from(e: utils::ser::Error) -> Self { - Self::Boxed(Box::new(e)) - } -} -impl From for LefError { - fn from(e: std::io::Error) -> Self { - Self::Boxed(Box::new(e)) - } -} -impl From for LefError { - fn from(e: rust_decimal::Error) -> Self { - Self::Boxed(Box::new(e)) - } -} -impl From for LefError { - /// Convert string-based errors by wrapping them - fn from(e: String) -> Self { - Self::Str(e) - } -} -impl From<&str> for LefError { - /// Convert string-based errors by wrapping them - fn from(e: &str) -> Self { - Self::Str(e.into()) - } -} -// One of these days, this way is gonna WORK, and we'll delete all these specific error-types above. -// impl From for LefError { -// /// Wrap External Errors -// fn from(e: E) -> Self { -// Self::Boxed(Box::new(e)) -// } -// } - -/// Lef21 Library-Wide Result Type -pub type LefResult = Result; - -// Implement the serialization to/from file trait for libraries and macros -impl utils::SerdeFile for LefLibrary {} -impl utils::SerdeFile for LefMacro {} diff --git a/lef21/src/read.rs b/lef21/src/read.rs index a9a8151..480f9c3 100644 --- a/lef21/src/read.rs +++ b/lef21/src/read.rs @@ -10,8 +10,14 @@ use std::io::Read; use std::path::Path; use std::str::Chars; +// Crates.io Imports +#[allow(unused_imports)] +use rust_decimal::prelude::*; +use serde::{Deserialize, Serialize}; + // Local imports -use super::*; +use super::data::*; +use super::utils::EnumStr; /// Parse LEF content from file `fname` pub fn parse_file(fname: impl AsRef) -> LefResult { diff --git a/lef21/src/write.rs b/lef21/src/write.rs index c9c2628..3dad63c 100644 --- a/lef21/src/write.rs +++ b/lef21/src/write.rs @@ -5,9 +5,14 @@ // Standard Lib Imports use std::io::Write; use std::ops::{AddAssign, SubAssign}; +use std::path::Path; + +// Layout21 Imports +use layout21utils as utils; +pub use utils::{enumstr, EnumStr, SerdeFile, SerializationFormat}; // Local imports -use super::*; +use super::data::*; /// Write a [LefLibrary] to file `fname`. /// Fields are written in the LEF-recommended order. @@ -128,7 +133,7 @@ impl<'wr> LefWriter<'wr> { self.write_symmetries(v)?; } // ROWPATTERN would be written here - // if site.row_pattern.is_some() { } + // if site.row_pattern.is_some() { } self.write_line(format_args_f!("{Size} {site.size.0} {By} {site.size.1} ;"))?; self.indent -= 1; self.write_line(format_args_f!("{End} {site.name} ; "))?; From 50d1e799499c6d3b422440f5c187458b8d598381 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Fri, 28 Jan 2022 13:08:18 -0800 Subject: [PATCH 05/28] `BoundBox` and `BoundBoxTrait` added to `raw::bbox` --- layout21raw/src/bbox.rs | 157 +++++++++++++++++++++++++++++++++++++++ layout21raw/src/gds.rs | 34 ++++++--- layout21raw/src/geom.rs | 26 ++++--- layout21raw/src/lef.rs | 4 +- layout21raw/src/lib.rs | 55 +++++--------- layout21raw/src/proto.rs | 20 +++-- 6 files changed, 233 insertions(+), 63 deletions(-) create mode 100644 layout21raw/src/bbox.rs diff --git a/layout21raw/src/bbox.rs b/layout21raw/src/bbox.rs new file mode 100644 index 0000000..fc3f18e --- /dev/null +++ b/layout21raw/src/bbox.rs @@ -0,0 +1,157 @@ +//! +//! # Rectangular Bounding Boxes and Associated Trait +//! + +// Crates.io +use serde::{Deserialize, Serialize}; + +// Local imports +use crate::{ + geom::{Point, Shape}, + Int, +}; + +/// # Rectangular Bounding Box +/// +/// Points `p0` and `p1` represent opposite corners of a bounding rectangle. +/// `p0` is always closest to negative-infinity, in both x and y, +/// and `p1` is always closest to positive-infinity. +/// +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct BoundBox { + pub p0: Point, + pub p1: Point, +} +impl BoundBox { + /// Create a new [BoundBox] from two [Point]s. + /// Callers are responsible for ensuring that p0.x <= p1.x, and p0.y <= p1.y. + fn new(p0: Point, p1: Point) -> Self { + Self { p0, p1 } + } + /// Create a new [BoundBox] from a single [Point]. + /// The resultant [BoundBox] comprises solely the point, having zero area. + pub fn from_point(pt: Point) -> Self { + Self { + p0: pt.clone(), + p1: pt.clone(), + } + } + /// Create a new [BoundBox] from two points + pub fn from_points(p0: Point, p1: Point) -> Self { + Self { + p0: Point::new(p0.x.min(p1.x), p0.y.min(p1.y)), + p1: Point::new(p0.x.max(p1.x), p0.y.max(p1.y)), + } + } + /// Create an empty, otherwise invalid [BoundBox] + pub fn empty() -> Self { + Self { + p0: Point::new(Int::MAX, Int::MAX), + p1: Point::new(Int::MIN, Int::MIN), + } + } + /// Boolean indication of whether a box is empty + pub fn is_empty(&self) -> bool { + self.p0.x > self.p1.x || self.p0.y > self.p1.y + } + /// Boolean indication of whether [Point] `pt` lies inside out box. + pub fn contains(&self, pt: &Point) -> bool { + self.p0.x <= pt.x && self.p1.x >= pt.x && self.p0.y <= pt.y && self.p1.y >= pt.y + } + /// Expand an existing [BoundBox] in all directions by `delta` + pub fn expand(&mut self, delta: Int) { + self.p0.x -= delta; + self.p0.y -= delta; + self.p1.x += delta; + self.p1.y += delta; + } + /// Get the box's size as an (x,y) tuple + pub fn size(&self) -> (Int, Int) { + (self.p1.x - self.p0.x, self.p1.y - self.p0.y) + } +} + +/// +/// # Bounding Box Trait +/// +/// Methods for interacting with [BoundBox]s. +/// Implementations for [Point]s, [Shape]s, and [BoundBox]s +/// enable geometric transformations such as union and intersection. +/// +pub trait BoundBoxTrait { + /// Compute the intersection with rectangular bounding box `bbox`. + /// Creates and returns a new [BoundBox]. + fn intersection(&self, bbox: &BoundBox) -> BoundBox; + /// Compute the union with rectangular bounding box `bbox`. + /// Creates and returns a new [BoundBox]. + fn union(&self, bbox: &BoundBox) -> BoundBox; + /// Compute a rectangular bounding box around the implementing type. + fn bbox(&self) -> BoundBox; +} + +impl BoundBoxTrait for BoundBox { + fn intersection(&self, bbox: &BoundBox) -> BoundBox { + let pmin = Point::new(self.p0.x.max(bbox.p0.x), self.p0.y.max(bbox.p0.y)); + let pmax = Point::new(self.p1.x.min(bbox.p1.x), self.p1.y.min(bbox.p1.y)); + if pmin.x > pmax.x || pmin.y > pmax.y { + return BoundBox::empty(); + } + BoundBox::new(pmin, pmax) + } + fn union(&self, bbox: &BoundBox) -> BoundBox { + BoundBox::new( + Point::new(self.p0.x.min(bbox.p0.x), self.p0.y.min(bbox.p0.y)), + Point::new(self.p1.x.max(bbox.p1.x), self.p1.y.max(bbox.p1.y)), + ) + } + fn bbox(&self) -> BoundBox { + self.clone() + } +} + +impl BoundBoxTrait for Point { + fn intersection(&self, bbox: &BoundBox) -> BoundBox { + if !bbox.contains(self) { + return BoundBox::empty(); + } + BoundBox::from_point(self.clone()) + } + fn union(&self, bbox: &BoundBox) -> BoundBox { + BoundBox::new( + Point::new(self.x.min(bbox.p0.x), self.y.min(bbox.p0.y)), + Point::new(self.x.max(bbox.p1.x), self.y.max(bbox.p1.y)), + ) + } + fn bbox(&self) -> BoundBox { + BoundBox::from_point(self.clone()) + } +} + +impl BoundBoxTrait for Shape { + fn intersection(&self, bbox: &BoundBox) -> BoundBox { + self.bbox().intersection(&bbox) + } + fn union(&self, bbox: &BoundBox) -> BoundBox { + self.bbox().union(&bbox) + } + fn bbox(&self) -> BoundBox { + match self { + Shape::Rect { ref p0, ref p1 } => BoundBox::from_points(p0.clone(), p1.clone()), + Shape::Poly { ref pts } => { + let mut bbox = BoundBox::empty(); + for pt in pts { + bbox = bbox.union(&pt.bbox()); + } + bbox + } + Shape::Path { ref pts, ref width } => { + let mut bbox = BoundBox::empty(); + for pt in pts { + bbox = bbox.union(&pt.bbox()); + } + bbox.expand(*width as Int); + bbox + } + } + } +} diff --git a/layout21raw/src/gds.rs b/layout21raw/src/gds.rs index 414f817..55094bb 100644 --- a/layout21raw/src/gds.rs +++ b/layout21raw/src/gds.rs @@ -13,14 +13,28 @@ use std::hash::Hash; use slotmap::{new_key_type, SlotMap}; // Local imports -use crate::utils::{ErrorContext, ErrorHelper, Ptr}; -use crate::{Abstract, AbstractPort, LayerKey, TextElement}; use crate::{ - Cell, Dir, Element, Instance, LayerPurpose, Layers, Layout, Library, Point, Shape, Units, + utils::{ErrorContext, ErrorHelper, Ptr}, + Abstract, AbstractPort, Cell, Dir, Element, Instance, Int, LayerKey, LayerPurpose, Layers, + Layout, LayoutError, LayoutResult, Library, Point, Shape, TextElement, Units, }; -use crate::{LayoutError, LayoutResult}; pub use gds21; +/// Additional [Library] methods for GDSII conversion +impl Library { + /// Convert to a GDSII Library + pub fn to_gds(&self) -> LayoutResult { + GdsExporter::export(&self) + } + /// Create from GDSII + pub fn from_gds( + gdslib: &gds21::GdsLibrary, + layers: Option>, + ) -> LayoutResult { + GdsImporter::import(&gdslib, layers) + } +} + new_key_type! { /// Keys for [Element] entries pub struct ElementKey; @@ -759,8 +773,8 @@ impl GdsImporter { self.fail("Unsupported Non-Rectangular GDS Array")?; } // Sort out the inter-element spacing - let mut xstep = (p1.x - p0.x) / isize::from(aref.cols); - let mut ystep = (p2.y - p0.y) / isize::from(aref.rows); + let mut xstep = (p1.x - p0.x) / Int::from(aref.cols); + let mut ystep = (p2.y - p0.y) / Int::from(aref.rows); // Incorporate the reflection/ rotation settings let mut angle = None; @@ -780,8 +794,8 @@ impl GdsImporter { let prev_xy = (i32::try_from(xstep)?, i32::try_from(ystep)?); let prev_xy = (f64::from(prev_xy.0), f64::from(prev_xy.1)); let a = a.to_radians(); // Rust `sin` and `cos` take radians, convert first - xstep = (prev_xy.0 * a.cos() - prev_xy.1 * a.sin()) as isize; - ystep = (prev_xy.0 * a.sin() + prev_xy.1 * a.cos()) as isize; + xstep = (prev_xy.0 * a.cos() - prev_xy.1 * a.sin()) as Int; + ystep = (prev_xy.0 * a.sin() + prev_xy.1 * a.cos()) as Int; // Set the same angle to each generated Instance angle = Some(a); @@ -791,9 +805,9 @@ impl GdsImporter { } // Create the Instances let mut insts = Vec::with_capacity((aref.rows * aref.cols) as usize); - for ix in 0..isize::from(aref.cols) { + for ix in 0..Int::from(aref.cols) { let x = p0.x + ix * xstep; - for iy in 0..isize::from(aref.rows) { + for iy in 0..Int::from(aref.rows) { let y = p0.y + iy * ystep; insts.push(Instance { inst_name: format!("{}[{}][{}]", cname, ix, iy), // `{array.name}[{col}][{row}]` diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs index db4f36b..3441f0a 100644 --- a/layout21raw/src/geom.rs +++ b/layout21raw/src/geom.rs @@ -6,24 +6,27 @@ //! // Std-Lib -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; // Crates.io use serde::{Deserialize, Serialize}; +// Local imports +use crate::Int; + /// # Point in two-dimensional layout-space #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct Point { - pub x: isize, - pub y: isize, + pub x: Int, + pub y: Int, } impl Point { /// Create a new [Point] from (x,y) coordinates - pub fn new(x: isize, y: isize) -> Self { + pub fn new(x: Int, y: Int) -> Self { Self { x, y } } /// Create a new [Point] which serves as an offset in direction `dir` - pub fn offset(val: isize, dir: Dir) -> Self { + pub fn offset(val: Int, dir: Dir) -> Self { match dir { Dir::Horiz => Self { x: val, y: 0 }, Dir::Vert => Self { x: 0, y: val }, @@ -44,7 +47,7 @@ impl Point { } } /// Get the coordinate associated with direction `dir` - pub fn coord(&self, dir: Dir) -> isize { + pub fn coord(&self, dir: Dir) -> Int { match dir { Dir::Horiz => self.x, Dir::Vert => self.y, @@ -59,8 +62,8 @@ impl Point { let x = trans.a[0][0] * xf + trans.a[0][1] * yf + trans.b[0]; let y = trans.a[1][0] * xf + trans.a[1][1] * yf + trans.b[1]; Self { - x: x.round() as isize, - y: y.round() as isize, + x: x.round() as Int, + y: y.round() as Int, } } } @@ -87,6 +90,11 @@ impl std::ops::Not for Dir { } } +/// # Shape +/// +/// The primary geometric primitive comprising raw layout. +/// Variants include [Rect], [Polygon], and [Path]. +/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum Shape { Rect { p0: Point, p1: Point }, @@ -190,7 +198,7 @@ impl Shape { // Only "Manhattan paths", i.e. those with segments solely running vertically or horizontally, are supported. // FIXME: even with this method, there are some small pieces at corners which we'll miss. // Whether these are relevant in real life, tbd. - let width = isize::try_from(*width).unwrap(); // FIXME: probably store these signed, check them on creation + let width = Int::try_from(*width).unwrap(); // FIXME: probably store these signed, check them on creation for k in 0..pts.len() - 1 { let rect = if pts[k].x == pts[k + 1].x { Shape::Rect { diff --git a/layout21raw/src/lef.rs b/layout21raw/src/lef.rs index d5a6a7f..b0e59ee 100644 --- a/layout21raw/src/lef.rs +++ b/layout21raw/src/lef.rs @@ -11,7 +11,7 @@ use std::convert::{TryFrom, TryInto}; // Local imports use crate::utils::{ErrorContext, ErrorHelper, Ptr}; use crate::{ - Abstract, AbstractPort, Cell, Element, Layer, LayerKey, LayerPurpose, Layers, LayoutError, + Abstract, AbstractPort, Cell, Element, Int, Layer, LayerKey, LayerPurpose, Layers, LayoutError, LayoutResult, Library, Point, Shape, Units, }; use lef21; @@ -428,7 +428,7 @@ impl LefImporter { )) } /// Import a distance coordinate, converting between units - fn import_dist(&mut self, lefdec: &lef21::LefDecimal) -> LayoutResult { + fn import_dist(&mut self, lefdec: &lef21::LefDecimal) -> LayoutResult { let scaled = lefdec * lef21::LefDecimal::from(self.dist_scale); if !scaled.fract().is_zero() { self.fail(format!( diff --git a/layout21raw/src/lib.rs b/layout21raw/src/lib.rs index 131e783..6b7495c 100644 --- a/layout21raw/src/lib.rs +++ b/layout21raw/src/lib.rs @@ -8,7 +8,6 @@ // Std-Lib use std::collections::{HashMap, HashSet}; -use std::convert::TryInto; use std::hash::Hash; // Crates.io @@ -17,10 +16,11 @@ use slotmap::{new_key_type, SlotMap}; // Internal modules & re-exports pub use layout21utils as utils; -use utils::ErrorContext; -use utils::{Ptr, PtrList}; +use utils::{ErrorContext, Ptr, PtrList}; pub mod geom; pub use geom::{Dir, Point, Shape, Transform}; +pub mod bbox; +pub use bbox::{BoundBox, BoundBoxTrait}; // Optional-feature modules #[cfg(feature = "gds")] @@ -33,6 +33,13 @@ pub mod proto; #[cfg(test)] mod tests; +/// # Location Integer Type-Alias +/// +/// Used for all layout spatial coordinates. +/// Designed for quickly swapping to other integer types, if we so desire. +/// +pub type Int = isize; + // Create key-types for each internal type stored in [SlotMap]s new_key_type! { /// Keys for [Layer] entries @@ -502,40 +509,6 @@ impl Library { ..Default::default() } } - /// Convert to a GDSII Library - #[cfg(feature = "gds")] - pub fn to_gds(&self) -> LayoutResult { - gds::GdsExporter::export(&self) - } - /// Create from GDSII - #[cfg(feature = "gds")] - pub fn from_gds( - gdslib: &gds::gds21::GdsLibrary, - layers: Option>, - ) -> LayoutResult { - gds::GdsImporter::import(&gdslib, layers) - } - /// Convert to ProtoBuf - #[cfg(feature = "proto")] - pub fn to_proto(&self) -> LayoutResult { - proto::ProtoExporter::export(&self) - } - /// Create from ProtoBuf, or anything convertible into a Proto Library - #[cfg(feature = "proto")] - pub fn from_proto(plib: T, layers: Option>) -> LayoutResult - where - // These trait bounds aren't pretty, but more or less say: - // * T is convertible into [proto::proto::Library] - // * Its conversion's error-type is convertible into [LayoutError] - // The "Into" form of the second condition would be something like: - // `::Error>: Into` - // but doesn't quite work, whereas "constraining" [LayoutError] does. - T: TryInto, - LayoutError: From<>::Error>, - { - let plib = plib.try_into()?; - proto::ProtoImporter::import(&plib, layers) - } } /// # Dependency-Orderer @@ -632,6 +605,14 @@ pub struct Layout { pub annotations: Vec, } impl Layout { + /// Create a rectangular [BoundBox] surrounding all elements in the [Layout]. + pub fn bbox(&self) -> BoundBox { + let mut bbox = BoundBox::empty(); + for elem in &self.elems { + bbox = elem.inner.union(&bbox); + } + bbox + } /// Flatten a [Layout], particularly its hierarchical instances, to a vector of [Element]s pub fn flatten(&self) -> LayoutResult> { // Kick off recursive calls, with the identity-transform applied for the top-level `layout` diff --git a/layout21raw/src/proto.rs b/layout21raw/src/proto.rs index 90e957b..2af4b15 100644 --- a/layout21raw/src/proto.rs +++ b/layout21raw/src/proto.rs @@ -14,14 +14,24 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; // Local imports -use crate::utils::Ptr; -use crate::utils::{ErrorContext, ErrorHelper}; use crate::{ - Abstract, AbstractPort, Cell, DepOrder, Element, Instance, LayerKey, LayerPurpose, Layers, + utils::{ErrorContext, ErrorHelper, Ptr}, + Abstract, AbstractPort, Cell, DepOrder, Element, Instance, Int, LayerKey, LayerPurpose, Layers, Layout, LayoutError, LayoutResult, Library, Point, Shape, TextElement, Units, }; pub use layout21protos as proto; +/// Additional [Library] methods for converting to/from proto-format +impl Library { + /// Convert to ProtoBuf + pub fn to_proto(&self) -> LayoutResult { + ProtoExporter::export(&self) + } + /// Create from ProtoBuf + pub fn from_proto(plib: proto::Library, layers: Option>) -> LayoutResult { + ProtoImporter::import(&plib, layers) + } +} /// # ProtoBuf Exporter #[derive(Debug)] pub struct ProtoExporter<'lib> { @@ -485,8 +495,8 @@ impl ProtoImporter { Some(ref p) => self.import_point(p), None => self.fail("Invalid proto::Rectangle with no location"), }?; - let width = isize::try_from(prect.width)?; - let height = isize::try_from(prect.height)?; + let width = Int::try_from(prect.width)?; + let height = Int::try_from(prect.height)?; let p1 = Point::new(p0.x + width, p0.y + height); Ok(Shape::Rect { p0, p1 }) } From bd7b0bd83c75f45845a738ae3b6f5d2333b95216 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Mon, 31 Jan 2022 17:12:31 -0800 Subject: [PATCH 06/28] Break out `layout21raw::data`, `layout21raw::error` modules. Tweaks to `BoundBox` API --- layout21raw/src/bbox.rs | 10 +- layout21raw/src/data.rs | 593 ++++++++++++++++++++++++++++++++ layout21raw/src/error.rs | 120 +++++++ layout21raw/src/lib.rs | 710 +-------------------------------------- 4 files changed, 734 insertions(+), 699 deletions(-) create mode 100644 layout21raw/src/data.rs create mode 100644 layout21raw/src/error.rs diff --git a/layout21raw/src/bbox.rs b/layout21raw/src/bbox.rs index fc3f18e..cb406a0 100644 --- a/layout21raw/src/bbox.rs +++ b/layout21raw/src/bbox.rs @@ -30,14 +30,14 @@ impl BoundBox { } /// Create a new [BoundBox] from a single [Point]. /// The resultant [BoundBox] comprises solely the point, having zero area. - pub fn from_point(pt: Point) -> Self { + pub fn from_point(pt: &Point) -> Self { Self { p0: pt.clone(), p1: pt.clone(), } } /// Create a new [BoundBox] from two points - pub fn from_points(p0: Point, p1: Point) -> Self { + pub fn from_points(p0: &Point, p1: &Point) -> Self { Self { p0: Point::new(p0.x.min(p1.x), p0.y.min(p1.y)), p1: Point::new(p0.x.max(p1.x), p0.y.max(p1.y)), @@ -114,7 +114,7 @@ impl BoundBoxTrait for Point { if !bbox.contains(self) { return BoundBox::empty(); } - BoundBox::from_point(self.clone()) + BoundBox::from_point(self) } fn union(&self, bbox: &BoundBox) -> BoundBox { BoundBox::new( @@ -123,7 +123,7 @@ impl BoundBoxTrait for Point { ) } fn bbox(&self) -> BoundBox { - BoundBox::from_point(self.clone()) + BoundBox::from_point(self) } } @@ -136,7 +136,7 @@ impl BoundBoxTrait for Shape { } fn bbox(&self) -> BoundBox { match self { - Shape::Rect { ref p0, ref p1 } => BoundBox::from_points(p0.clone(), p1.clone()), + Shape::Rect { ref p0, ref p1 } => BoundBox::from_points(p0, p1), Shape::Poly { ref pts } => { let mut bbox = BoundBox::empty(); for pt in pts { diff --git a/layout21raw/src/data.rs b/layout21raw/src/data.rs new file mode 100644 index 0000000..139cd7c --- /dev/null +++ b/layout21raw/src/data.rs @@ -0,0 +1,593 @@ +//! +//! # Raw Layout Data Model +//! +//! Defines the primary structures for representation of "raw" geometry-based IC layout, +//! including [Library], [Cell], [Layout], and related types. +//! + +// Std-Lib +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; + +// Crates.io +use serde::{Deserialize, Serialize}; +use slotmap::{new_key_type, SlotMap}; + +// Local Imports +use crate::{ + bbox::{BoundBox, BoundBoxTrait}, + error::{LayoutError, LayoutResult}, + geom::{Point, Shape, Transform}, + utils::{Ptr, PtrList}, +}; + +/// # Location Integer Type-Alias +/// +/// Used for all layout spatial coordinates. +/// Designed for quickly swapping to other integer types, if we so desire. +/// +pub type Int = isize; + +// Create key-types for each internal type stored in [SlotMap]s +new_key_type! { + /// Keys for [Layer] entries + pub struct LayerKey; +} + +/// Distance Units Enumeration +/// FIXME: deprecate in favor of [SiUnits] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum Units { + /// Micrometers, or microns for we olde folke + Micro, + /// Nanometers + Nano, + /// Angstroms + Angstrom, + /// Picometers + Pico, +} +impl Default for Units { + /// Default units are nanometers + fn default() -> Units { + Units::Nano + } +} +/// Enumerated SI Units +#[allow(dead_code)] // FIXME! +enum SiUnits { + Yocto, // E-24 + Zepto, // E-21 + Atto, // E-18 + Femto, // E-15 + Pico, // E-12 + Nano, // E-9 + Micro, // E-6 + Milli, // E-3 + Centi, // E-2 + Deci, // E-1 + Deca, // E1 + Hecto, // E2 + Kilo, // E3 + Mega, // E6 + Giga, // E9 + Tera, // E12 + Peta, // E15 + Exa, // E18 + Zetta, // E21 + Yotta, // E24 +} +impl Default for SiUnits { + /// Default units are nano-scale + #[allow(dead_code)] // FIXME! + fn default() -> SiUnits { + SiUnits::Nano + } +} +impl SiUnits { + /// Get the exponent of the unit + #[allow(dead_code)] // FIXME! + fn exp(&self) -> isize { + use SiUnits::*; + match self { + Yocto => -24, + Zepto => -21, + Atto => -18, + Femto => -15, + Pico => -12, + Nano => -9, + Micro => -6, + Milli => -3, + Centi => -2, + Deci => -1, + Deca => 1, + Hecto => 2, + Kilo => 3, + Mega => 6, + Giga => 9, + Tera => 12, + Peta => 15, + Exa => 18, + Zetta => 21, + Yotta => 24, + } + } +} + +/// Instance of another Cell +#[derive(Debug, Clone)] +pub struct Instance { + /// Instance Name + pub inst_name: String, + /// Cell Definition Reference + pub cell: Ptr, + /// Location of `cell` origin + /// regardless of rotation or reflection + pub loc: Point, + /// Vertical reflection, + /// applied *before* rotation + pub reflect_vert: bool, + /// Angle of rotation (degrees), + /// Clockwise and applied *after* reflection + pub angle: Option, +} + +/// # Layer Set & Manager +/// +/// Keep track of active layers, and index them by name and number. +/// +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Layers { + pub slots: SlotMap, + pub nums: HashMap, + pub names: HashMap, +} +impl Layers { + /// Add a [Layer] to our slot-map and number-map, and name-map + pub fn add(&mut self, layer: Layer) -> LayerKey { + // FIXME: conflicting numbers and/or names, at least some of which tend to happen, over-write each other. + // Sort out the desired behavior here. + let num = layer.layernum; + let name = layer.name.clone(); + let key = self.slots.insert(layer); + self.nums.insert(num, key.clone()); + if let Some(s) = name { + self.names.insert(s, key.clone()); + } + key + } + /// Get the next-available (lowest) layer number + pub fn nextnum(&self) -> LayoutResult { + for k in 0..i16::MAX { + if !self.nums.contains_key(&k) { + return Ok(k); + } + } + LayoutError::fail("No more layer numbers available") + } + /// Get a reference to the [LayerKey] for layer-number `num` + pub fn keynum(&self, num: i16) -> Option { + self.nums.get(&num).map(|x| x.clone()) + } + /// Get a reference to the [LayerKey] layer-name `name` + pub fn keyname(&self, name: impl Into) -> Option { + self.names.get(&name.into()).map(|x| x.clone()) + } + /// Get a reference to [Layer] number `num` + pub fn num(&self, num: i16) -> Option<&Layer> { + let key = self.nums.get(&num)?; + self.slots.get(*key) + } + /// Get a reference to [Layer] name `name` + pub fn name(&self, name: &str) -> Option<&Layer> { + let key = self.names.get(name)?; + self.slots.get(*key) + } + /// Get the name of `layerkey` + pub fn get_name(&self, layerkey: LayerKey) -> Option<&String> { + let layer = self.slots.get(layerkey)?; + layer.name.as_ref() + } + /// Get a reference to [Layer] from [LayerKey] `key` + pub fn get(&self, key: LayerKey) -> Option<&Layer> { + self.slots.get(key) + } + /// Get the ([LayerKey], [LayerPurpose]) objects for numbers (`layernum`, `purposenum`) if present. + /// Inserts a new [Layer] if `layernum` is not present. + /// Returns `LayerPurpose::Other(purposenum)` if `purposenum` is not present on that layer. + pub fn get_or_insert( + &mut self, + layernum: i16, + purposenum: i16, + ) -> LayoutResult<(LayerKey, LayerPurpose)> { + // Get the [LayerKey] for `layernum`, creating the [Layer] if it doesn't exist. + let key = match self.keynum(layernum) { + Some(key) => key.clone(), + None => self.add(Layer::from_num(layernum)), + }; + // Slightly awkwardly, get that [Layer] (back), so we can get or add a [LayerPurpose] + let layer = self + .slots + .get_mut(key) + .ok_or(LayoutError::msg("Layer Not Found"))?; + // Get or create the corresponding [LayerPurpose] + let purpose = match layer.purpose(purposenum) { + Some(purpose) => purpose.clone(), + None => { + // Create a new anonymous/ numbered layer-purpose + let purpose = LayerPurpose::Other(purposenum); + layer.add_purpose(purposenum, purpose.clone())?; + purpose + } + }; + Ok((key, purpose)) + } + /// Get a shared reference to the internal <[LayerKey], [Layer]> map + pub fn slots(&self) -> &SlotMap { + &self.slots + } +} +/// Layer-Purpose Enumeration +/// Includes the common use-cases for each shape, +/// and two "escape hatches", one named and one not. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum LayerPurpose { + // First-class enumerated purposes + Drawing, + Pin, + Label, + Obstruction, + Outline, + /// Named purpose, not first-class supported + Named(String, i16), + /// Other purpose, not first-class supported nor named + Other(i16), +} +/// # Layer Specification +/// As in seemingly every layout system, this uses two numbers to identify each layer. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct LayerSpec(i16, i16); +impl LayerSpec { + pub fn new(n1: i16, n2: i16) -> Self { + Self(n1, n2) + } +} +/// # Per-Layer Datatype Specification +/// Includes the datatypes used for each category of element on layer `layernum` +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct Layer { + /// Layer Number + pub layernum: i16, + /// Layer Name + pub name: Option, + /// Number => Purpose Lookup + purps: HashMap, + /// Purpose => Number Lookup + nums: HashMap, +} +impl Layer { + /// Create a new [Layer] with the given `layernum` and `name` + pub fn new(layernum: i16, name: impl Into) -> Self { + Self { + layernum, + name: Some(name.into()), + ..Default::default() + } + } + /// Create a new [Layer] with the given `layernum`. + pub fn from_num(layernum: i16) -> Self { + Self { + layernum, + ..Default::default() + } + } + /// Create a new [Layer] purpose-numbers `pairs`. + pub fn from_pairs(layernum: i16, pairs: &[(i16, LayerPurpose)]) -> LayoutResult { + let mut layer = Self::from_num(layernum); + for (num, purpose) in pairs { + layer.add_purpose(*num, purpose.clone())?; + } + Ok(layer) + } + /// Add purpose-numbers `pairs`. Consumes and returns `self` for chainability. + pub fn add_pairs(mut self, pairs: &[(i16, LayerPurpose)]) -> LayoutResult { + for (num, purpose) in pairs { + self.add_purpose(*num, purpose.clone())?; + } + Ok(self) + } + /// Add a new [LayerPurpose] + pub fn add_purpose(&mut self, num: i16, purp: LayerPurpose) -> LayoutResult<()> { + // If we get a numbered purpose, make sure its id matches `num`. + match purp { + LayerPurpose::Named(_, k) | LayerPurpose::Other(k) => { + if k != num { + LayoutError::fail("Invalid LayerPurpose")?; + } + } + _ => (), + }; + self.purps.insert(num, purp.clone()); + self.nums.insert(purp, num); + Ok(()) + } + /// Retrieve purpose-number `num` + pub fn purpose(&self, num: i16) -> Option<&LayerPurpose> { + self.purps.get(&num) + } + /// Retrieve the purpose-number for this layer and [Purpose] `purpose` + pub fn num(&self, purpose: &LayerPurpose) -> Option { + self.nums.get(purpose).copied() + } +} + +/// Raw Abstract-Layout +/// Contains geometric [Element]s generally representing pins and blockages +/// Does not contain instances, arrays, or layout-implementation details +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Abstract { + /// Cell Name + pub name: String, + /// Outline + pub outline: Element, + /// Ports + pub ports: Vec, + /// Blockages + pub blockages: HashMap>, +} +impl Abstract { + /// Create a new [Abstract] with the given `name` + pub fn new(name: impl Into, outline: Element) -> Self { + let name = name.into(); + Self { + name, + outline, + ports: Vec::new(), + blockages: HashMap::new(), + } + } +} +/// # Port Element for [Abstract]s +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct AbstractPort { + /// Net Name + pub net: String, + /// Shapes, with paired [Layer] keys + pub shapes: HashMap>, +} +impl AbstractPort { + /// Create a new [AbstractPort] with the given `name` + pub fn new(net: impl Into) -> Self { + let net = net.into(); + Self { + net, + shapes: HashMap::new(), + } + } +} + +/// # Raw Layout Library +/// A collection of cell-definitions and sub-library definitions +#[derive(Debug, Clone, Default)] +pub struct Library { + /// Library Name + pub name: String, + /// Distance Units + pub units: Units, + /// Layer Definitions + pub layers: Ptr, + /// Cell Definitions + pub cells: PtrList, +} +impl Library { + /// Create a new and empty Library + pub fn new(name: impl Into, units: Units) -> Self { + Self { + name: name.into(), + units, + ..Default::default() + } + } +} + +/// # Dependency-Orderer +#[derive(Debug)] +pub struct DepOrder<'lib> { + // FIXME: move to utils shared version + lib: &'lib Library, + stack: Vec>, + seen: HashSet>, +} +impl<'lib> DepOrder<'lib> { + pub fn order(lib: &'lib Library) -> Vec> { + let mut myself = Self { + lib, + stack: Vec::new(), + seen: HashSet::new(), + }; + for cell in myself.lib.cells.iter() { + myself.push(cell); + } + myself.stack + } + pub fn push(&mut self, ptr: &Ptr) { + // If the Cell hasn't already been visited, depth-first search it + if !self.seen.contains(&ptr) { + // Read the cell-pointer + let cell = ptr.read().unwrap(); + // If the cell has an implementation, visit its [Instance]s before inserting it + if let Some(layout) = &cell.layout { + for inst in &layout.insts { + self.push(&inst.cell); + } + } + // And insert the cell (pointer) itself + self.seen.insert(Ptr::clone(ptr)); + self.stack.push(Ptr::clone(ptr)); + } + } +} + +/// Collection of the Views describing a Cell +#[derive(Debug, Default, Clone)] +pub struct Cell { + // Cell Name + pub name: String, + // Layout Abstract + pub abs: Option, + // Layout Implementation + pub layout: Option, +} +impl Cell { + /// Create a new and empty Cell named `name` + pub fn new(name: impl Into) -> Self { + let name = name.into(); + Self { + name, + ..Default::default() + } + } +} +impl From for Cell { + fn from(src: Abstract) -> Self { + Self { + name: src.name.clone(), + abs: Some(src), + ..Default::default() + } + } +} +impl From for Cell { + fn from(src: Layout) -> Self { + Self { + name: src.name.clone(), + layout: Some(src), + ..Default::default() + } + } +} + +/// # Raw-Layout Implementation +/// +/// The geometric-level layout-definition of a [Cell]. +/// Comprised of geometric [Element]s and instances of other [Cell] [Layout]s. +/// +#[derive(Debug, Clone, Default)] +pub struct Layout { + /// Cell Name + pub name: String, + /// Instances + pub insts: Vec, + /// Primitive/ Geometric Elements + pub elems: Vec, + /// Text Annotations + pub annotations: Vec, +} +impl Layout { + /// Create a rectangular [BoundBox] surrounding all elements in the [Layout]. + pub fn bbox(&self) -> BoundBox { + let mut bbox = BoundBox::empty(); + for elem in &self.elems { + bbox = elem.inner.union(&bbox); + } + bbox + } + /// Flatten a [Layout], particularly its hierarchical instances, to a vector of [Element]s + pub fn flatten(&self) -> LayoutResult> { + // Kick off recursive calls, with the identity-transform applied for the top-level `layout` + let mut elems = Vec::new(); + flatten_helper(self, &Transform::identity(), &mut elems)?; + Ok(elems) + } +} +/// Internal helper and core logic for [Layout::flatten]. +fn flatten_helper( + layout: &Layout, + trans: &Transform, + elems: &mut Vec, +) -> LayoutResult<()> { + // Translate each geometric element + for elem in layout.elems.iter() { + // Clone all other data (layer, net, etc.) + // FIXME: hierarchy flattening of net labels + let mut new_elem = elem.clone(); + // And translate the inner shape by `trans` + new_elem.inner = elem.inner.transform(trans); + elems.push(new_elem); + } + // Note text-valued "annotations" are ignored + + // Visit all of `layout`'s instances, recursively getting their elements + for inst in &layout.insts { + // Get the cell's layout-definition, or fail + let cell = inst.cell.read()?; + let layout = cell.layout.as_ref().unwrap(); + + // Create a new [Transform], cascading the parent's and instance's + let inst_trans = Transform::from_instance(&inst.loc, inst.reflect_vert, inst.angle); + let trans = Transform::cascade(&trans, &inst_trans); + + // And recursively add its elements + flatten_helper(&layout, &trans, elems)?; + } + Ok(()) +} + +/// # Text Annotation +/// +/// Note [layout21::raw::TextElement]s are "layer-less", +/// i.e. they do not sit on different layers, +/// and do not describe connectivity or generate pins. +/// These are purely annotations in the sense of "design notes". +/// +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct TextElement { + /// String Value + pub string: String, + /// Location + pub loc: Point, +} +/// # Primitive Geometric Element +/// +/// Primary unit of [Layout] definition. +/// Combines a geometric [Shape] with a z-axis [Layer], +/// and optional net connectivity annotation. +/// +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Element { + /// Net Name + pub net: Option, + /// Layer (Reference) + pub layer: LayerKey, + /// Purpose + pub purpose: LayerPurpose, + /// Shape + pub inner: Shape, +} + +/// Location, orientation, and angular rotation for an [Instance] +/// Note these fields exist "flat" in [Instance] as well, +/// and are grouped here for convenience. +pub struct InstancePlace { + /// Location of `cell` origin + /// regardless of rotation or reflection + pub loc: Point, + /// Vertical reflection, + /// applied *before* rotation + pub reflect_vert: bool, + /// Angle of rotation (degrees), + /// Clockwise and applied *after* reflection + pub angle: Option, +} + +// pub struct Flatten<'l> { +// lib: &'l Library, +// top: &'l Layout, +// } + +// impl Iterator for Flatten { +// type Item = Element; +// fn next(&mut self) -> Option { +// unimplemented!() +// } +// } diff --git a/layout21raw/src/error.rs b/layout21raw/src/error.rs new file mode 100644 index 0000000..2b2ad99 --- /dev/null +++ b/layout21raw/src/error.rs @@ -0,0 +1,120 @@ +//! +//! # Layout Result and Error Types +//! + +// Local Imports +pub use crate::utils::{self, ErrorContext}; + +/// # [LayoutError] Result Type +pub type LayoutResult = Result; + +/// +/// # Layout Error Enumeration +/// +pub enum LayoutError { + /// Error Exporting to Foreign Format + Export { + message: String, + stack: Vec, + }, + /// Error Importing from Foreign Format + Import { + message: String, + stack: Vec, + }, + /// Conversion Errors, with Boxed External Error + Conversion { + message: String, + err: Box, + stack: Vec, + }, + /// Boxed External Errors + Boxed(Box), + /// Uncategorized Error, with String Message + Str(String), + /// # [Ptr] Locking + /// Caused by trouble with a [Ptr]: either deadlock, or panic while holding a lock. + /// Generally caused by a [std::sync::PoisonError], which is not forwardable due to lifetime constraints. + PtrLock, +} +impl LayoutError { + /// Create a [LayoutError::Message] from anything String-convertible + pub fn msg(s: impl Into) -> Self { + Self::Str(s.into()) + } + /// Create an error-variant [Result] of our [LayoutError::Message] variant from anything String-convertible + pub fn fail(s: impl Into) -> Result { + Err(Self::msg(s)) + } +} +impl std::fmt::Debug for LayoutError { + /// Display a [LayoutError] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + LayoutError::Export { message, stack } => { + write!(f, "Export Error: \n - {} \n - {:?}", message, stack) + } + LayoutError::Import { message, stack } => { + write!(f, "Import Error: \n - {} \n - {:?}", message, stack) + } + LayoutError::Conversion { + message, + err, + stack, + } => write!( + f, + "Conversion Error: \n - {} \n - {} \n - {:?}", + message, err, stack + ), + LayoutError::Boxed(err) => err.fmt(f), + LayoutError::Str(err) => err.fmt(f), + LayoutError::PtrLock => write!(f, "[std::sync::PoisonError]"), + } + } +} +impl std::fmt::Display for LayoutError { + /// Display a [LayoutError] + /// Delegates to the [Debug] implementation + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} +impl std::error::Error for LayoutError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Boxed(e) => Some(&**e), + _ => None, + } + } +} + +impl From for LayoutError { + fn from(s: String) -> Self { + Self::Str(s) + } +} +impl From<&str> for LayoutError { + fn from(s: &str) -> Self { + Self::Str(s.to_string()) + } +} +impl From for LayoutError { + fn from(e: std::num::TryFromIntError) -> Self { + Self::Boxed(Box::new(e)) + } +} +impl From for LayoutError { + fn from(e: utils::ser::Error) -> Self { + Self::Boxed(Box::new(e)) + } +} +impl From> for LayoutError { + fn from(_e: std::sync::PoisonError) -> Self { + Self::PtrLock + } +} +impl From> for LayoutError { + fn from(e: Box) -> Self { + Self::Boxed(e) + } +} diff --git a/layout21raw/src/lib.rs b/layout21raw/src/lib.rs index 6b7495c..ea12938 100644 --- a/layout21raw/src/lib.rs +++ b/layout21raw/src/lib.rs @@ -1,26 +1,27 @@ //! //! # Raw Layout //! -//! The most general, most flexible representation layer in layout21. +//! The most general and lowest-level representation layer in layout21. //! Consists of geometric primitives and instances of other layout cells, //! much akin to nearly any legacy layout system. //! -// Std-Lib -use std::collections::{HashMap, HashSet}; -use std::hash::Hash; - -// Crates.io -use serde::{Deserialize, Serialize}; -use slotmap::{new_key_type, SlotMap}; +// Internal modules +pub mod bbox; +pub mod data; +pub mod error; +pub mod geom; -// Internal modules & re-exports +// Re-exports +#[doc(inline)] +pub use bbox::*; +#[doc(inline)] +pub use data::*; +#[doc(inline)] +pub use error::*; +#[doc(inline)] +pub use geom::*; pub use layout21utils as utils; -use utils::{ErrorContext, Ptr, PtrList}; -pub mod geom; -pub use geom::{Dir, Point, Shape, Transform}; -pub mod bbox; -pub use bbox::{BoundBox, BoundBoxTrait}; // Optional-feature modules #[cfg(feature = "gds")] @@ -29,686 +30,7 @@ pub mod gds; pub mod lef; #[cfg(feature = "proto")] pub mod proto; + // Unit tests #[cfg(test)] mod tests; - -/// # Location Integer Type-Alias -/// -/// Used for all layout spatial coordinates. -/// Designed for quickly swapping to other integer types, if we so desire. -/// -pub type Int = isize; - -// Create key-types for each internal type stored in [SlotMap]s -new_key_type! { - /// Keys for [Layer] entries - pub struct LayerKey; -} -/// LayoutError-Specific Result Type -pub type LayoutResult = Result; - -/// -/// # Layout Error Enumeration -/// -pub enum LayoutError { - /// Error Exporting to Foreign Format - Export { - message: String, - stack: Vec, - }, - /// Error Importing from Foreign Format - Import { - message: String, - stack: Vec, - }, - /// Conversion Errors, with Boxed External Error - Conversion { - message: String, - err: Box, - stack: Vec, - }, - /// Boxed External Errors - Boxed(Box), - /// Uncategorized Error, with String Message - Str(String), - /// # [Ptr] Locking - /// Caused by trouble with a [Ptr]: either deadlock, or panic while holding a lock. - /// Generally caused by a [std::sync::PoisonError], which is not forwardable due to lifetime constraints. - PtrLock, -} -impl LayoutError { - /// Create a [LayoutError::Message] from anything String-convertible - pub fn msg(s: impl Into) -> Self { - Self::Str(s.into()) - } - /// Create an error-variant [Result] of our [LayoutError::Message] variant from anything String-convertible - pub fn fail(s: impl Into) -> Result { - Err(Self::msg(s)) - } -} -impl std::fmt::Debug for LayoutError { - /// Display a [LayoutError] - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - LayoutError::Export { message, stack } => { - write!(f, "Export Error: \n - {} \n - {:?}", message, stack) - } - LayoutError::Import { message, stack } => { - write!(f, "Import Error: \n - {} \n - {:?}", message, stack) - } - LayoutError::Conversion { - message, - err, - stack, - } => write!( - f, - "Conversion Error: \n - {} \n - {} \n - {:?}", - message, err, stack - ), - LayoutError::Boxed(err) => err.fmt(f), - LayoutError::Str(err) => err.fmt(f), - LayoutError::PtrLock => write!(f, "[std::sync::PoisonError]"), - } - } -} -impl std::fmt::Display for LayoutError { - /// Display a [LayoutError] - /// Delegates to the [Debug] implementation - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) - } -} -impl std::error::Error for LayoutError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Boxed(e) => Some(&**e), - _ => None, - } - } -} - -impl From for LayoutError { - fn from(s: String) -> Self { - Self::Str(s) - } -} -impl From<&str> for LayoutError { - fn from(s: &str) -> Self { - Self::Str(s.to_string()) - } -} -impl From for LayoutError { - fn from(e: std::num::TryFromIntError) -> Self { - Self::Boxed(Box::new(e)) - } -} -impl From for LayoutError { - fn from(e: utils::ser::Error) -> Self { - Self::Boxed(Box::new(e)) - } -} -impl From> for LayoutError { - fn from(_e: std::sync::PoisonError) -> Self { - Self::PtrLock - } -} -impl From> for LayoutError { - fn from(e: Box) -> Self { - Self::Boxed(e) - } -} -/// Distance Units Enumeration -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum Units { - /// Micrometers, or microns for we olde folke - Micro, - /// Nanometers - Nano, - /// Angstroms - Angstrom, - /// Picometers - Pico, -} -impl Default for Units { - /// Default units are nanometers - fn default() -> Units { - Units::Nano - } -} -/// Enumerated SI Units -#[allow(dead_code)] // FIXME! -enum SiUnits { - Yocto, // E-24 - Zepto, // E-21 - Atto, // E-18 - Femto, // E-15 - Pico, // E-12 - Nano, // E-9 - Micro, // E-6 - Milli, // E-3 - Centi, // E-2 - Deci, // E-1 - Deca, // E1 - Hecto, // E2 - Kilo, // E3 - Mega, // E6 - Giga, // E9 - Tera, // E12 - Peta, // E15 - Exa, // E18 - Zetta, // E21 - Yotta, // E24 -} -impl Default for SiUnits { - /// Default units are nano-scale - #[allow(dead_code)] // FIXME! - fn default() -> SiUnits { - SiUnits::Nano - } -} -impl SiUnits { - /// Get the exponent of the unit - #[allow(dead_code)] // FIXME! - fn exp(&self) -> isize { - use SiUnits::*; - match self { - Yocto => -24, - Zepto => -21, - Atto => -18, - Femto => -15, - Pico => -12, - Nano => -9, - Micro => -6, - Milli => -3, - Centi => -2, - Deci => -1, - Deca => 1, - Hecto => 2, - Kilo => 3, - Mega => 6, - Giga => 9, - Tera => 12, - Peta => 15, - Exa => 18, - Zetta => 21, - Yotta => 24, - } - } -} - -/// Instance of another Cell -#[derive(Debug, Clone)] -pub struct Instance { - /// Instance Name - pub inst_name: String, - /// Cell Definition Reference - pub cell: Ptr, - /// Location of `cell` origin - /// regardless of rotation or reflection - pub loc: Point, - /// Vertical reflection, - /// applied *before* rotation - pub reflect_vert: bool, - /// Angle of rotation (degrees), - /// Clockwise and applied *after* reflection - pub angle: Option, -} - -/// Layer Set & Manager -/// Keep track of active layers, and index them by name and number. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Layers { - slots: SlotMap, - nums: HashMap, - names: HashMap, -} -impl Layers { - /// Add a [Layer] to our slot-map and number-map, and name-map - pub fn add(&mut self, layer: Layer) -> LayerKey { - // FIXME: conflicting numbers and/or names, at least some of which tend to happen, over-write each other. - // Sort out the desired behavior here. - let num = layer.layernum; - let name = layer.name.clone(); - let key = self.slots.insert(layer); - self.nums.insert(num, key.clone()); - if let Some(s) = name { - self.names.insert(s, key.clone()); - } - key - } - /// Get the next-available (lowest) layer number - pub fn nextnum(&self) -> LayoutResult { - for k in 0..i16::MAX { - if !self.nums.contains_key(&k) { - return Ok(k); - } - } - LayoutError::fail("No more layer numbers available") - } - /// Get a reference to the [LayerKey] for layer-number `num` - pub fn keynum(&self, num: i16) -> Option { - self.nums.get(&num).map(|x| x.clone()) - } - /// Get a reference to the [LayerKey] layer-name `name` - pub fn keyname(&self, name: impl Into) -> Option { - self.names.get(&name.into()).map(|x| x.clone()) - } - /// Get a reference to [Layer] number `num` - pub fn num(&self, num: i16) -> Option<&Layer> { - let key = self.nums.get(&num)?; - self.slots.get(*key) - } - /// Get a reference to [Layer] name `name` - pub fn name(&self, name: &str) -> Option<&Layer> { - let key = self.names.get(name)?; - self.slots.get(*key) - } - /// Get the name of `layerkey` - pub fn get_name(&self, layerkey: LayerKey) -> Option<&String> { - let layer = self.slots.get(layerkey)?; - layer.name.as_ref() - } - /// Get a reference to [Layer] from [LayerKey] `key` - pub fn get(&self, key: LayerKey) -> Option<&Layer> { - self.slots.get(key) - } - /// Get the ([LayerKey], [LayerPurpose]) objects for numbers (`layernum`, `purposenum`) if present. - /// Inserts a new [Layer] if `layernum` is not present. - /// Returns `LayerPurpose::Other(purposenum)` if `purposenum` is not present on that layer. - pub fn get_or_insert( - &mut self, - layernum: i16, - purposenum: i16, - ) -> LayoutResult<(LayerKey, LayerPurpose)> { - // Get the [LayerKey] for `layernum`, creating the [Layer] if it doesn't exist. - let key = match self.keynum(layernum) { - Some(key) => key.clone(), - None => self.add(Layer::from_num(layernum)), - }; - // Slightly awkwardly, get that [Layer] (back), so we can get or add a [LayerPurpose] - let layer = self - .slots - .get_mut(key) - .ok_or(LayoutError::msg("Layer Not Found"))?; - // Get or create the corresponding [LayerPurpose] - let purpose = match layer.purpose(purposenum) { - Some(purpose) => purpose.clone(), - None => { - // Create a new anonymous/ numbered layer-purpose - let purpose = LayerPurpose::Other(purposenum); - layer.add_purpose(purposenum, purpose.clone())?; - purpose - } - }; - Ok((key, purpose)) - } - /// Get a shared reference to the internal <[LayerKey], [Layer]> map - pub fn slots(&self) -> &SlotMap { - &self.slots - } -} -/// Layer-Purpose Enumeration -/// Includes the common use-cases for each shape, -/// and two "escape hatches", one named and one not. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum LayerPurpose { - // First-class enumerated purposes - Drawing, - Pin, - Label, - Obstruction, - Outline, - /// Named purpose, not first-class supported - Named(String, i16), - /// Other purpose, not first-class supported nor named - Other(i16), -} -/// # Layer Specification -/// As in seemingly every layout system, this uses two numbers to identify each layer. -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct LayerSpec(i16, i16); -impl LayerSpec { - pub fn new(n1: i16, n2: i16) -> Self { - Self(n1, n2) - } -} -/// # Per-Layer Datatype Specification -/// Includes the datatypes used for each category of element on layer `layernum` -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] -pub struct Layer { - /// Layer Number - pub layernum: i16, - /// Layer Name - pub name: Option, - /// Number => Purpose Lookup - purps: HashMap, - /// Purpose => Number Lookup - nums: HashMap, -} -impl Layer { - /// Create a new [Layer] with the given `layernum` and `name` - pub fn new(layernum: i16, name: impl Into) -> Self { - Self { - layernum, - name: Some(name.into()), - ..Default::default() - } - } - /// Create a new [Layer] with the given `layernum`. - pub fn from_num(layernum: i16) -> Self { - Self { - layernum, - ..Default::default() - } - } - /// Create a new [Layer] purpose-numbers `pairs`. - pub fn from_pairs(layernum: i16, pairs: &[(i16, LayerPurpose)]) -> LayoutResult { - let mut layer = Self::from_num(layernum); - for (num, purpose) in pairs { - layer.add_purpose(*num, purpose.clone())?; - } - Ok(layer) - } - /// Add purpose-numbers `pairs`. Consumes and returns `self` for chainability. - pub fn add_pairs(mut self, pairs: &[(i16, LayerPurpose)]) -> LayoutResult { - for (num, purpose) in pairs { - self.add_purpose(*num, purpose.clone())?; - } - Ok(self) - } - /// Add a new [LayerPurpose] - pub fn add_purpose(&mut self, num: i16, purp: LayerPurpose) -> LayoutResult<()> { - // If we get a numbered purpose, make sure its id matches `num`. - match purp { - LayerPurpose::Named(_, k) | LayerPurpose::Other(k) => { - if k != num { - LayoutError::fail("Invalid LayerPurpose")?; - } - } - _ => (), - }; - self.purps.insert(num, purp.clone()); - self.nums.insert(purp, num); - Ok(()) - } - /// Retrieve purpose-number `num` - pub fn purpose(&self, num: i16) -> Option<&LayerPurpose> { - self.purps.get(&num) - } - /// Retrieve the purpose-number for this layer and [Purpose] `purpose` - pub fn num(&self, purpose: &LayerPurpose) -> Option { - self.nums.get(purpose).copied() - } -} - -/// Raw Abstract-Layout -/// Contains geometric [Element]s generally representing pins and blockages -/// Does not contain instances, arrays, or layout-implementation details -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Abstract { - /// Cell Name - pub name: String, - /// Outline - pub outline: Element, - /// Ports - pub ports: Vec, - /// Blockages - pub blockages: HashMap>, -} -impl Abstract { - /// Create a new [Abstract] with the given `name` - pub fn new(name: impl Into, outline: Element) -> Self { - let name = name.into(); - Self { - name, - outline, - ports: Vec::new(), - blockages: HashMap::new(), - } - } -} -/// # Port Element for [Abstract]s -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct AbstractPort { - /// Net Name - pub net: String, - /// Shapes, with paired [Layer] keys - pub shapes: HashMap>, -} -impl AbstractPort { - /// Create a new [AbstractPort] with the given `name` - pub fn new(net: impl Into) -> Self { - let net = net.into(); - Self { - net, - shapes: HashMap::new(), - } - } -} - -/// # Raw Layout Library -/// A collection of cell-definitions and sub-library definitions -#[derive(Debug, Clone, Default)] -pub struct Library { - /// Library Name - pub name: String, - /// Distance Units - pub units: Units, - /// Layer Definitions - pub layers: Ptr, - /// Cell Definitions - pub cells: PtrList, -} -impl Library { - /// Create a new and empty Library - pub fn new(name: impl Into, units: Units) -> Self { - Self { - name: name.into(), - units, - ..Default::default() - } - } -} - -/// # Dependency-Orderer -#[derive(Debug)] -pub struct DepOrder<'lib> { - // FIXME: move to utils shared version - lib: &'lib Library, - stack: Vec>, - seen: HashSet>, -} -impl<'lib> DepOrder<'lib> { - fn order(lib: &'lib Library) -> Vec> { - let mut myself = Self { - lib, - stack: Vec::new(), - seen: HashSet::new(), - }; - for cell in myself.lib.cells.iter() { - myself.push(cell); - } - myself.stack - } - fn push(&mut self, ptr: &Ptr) { - // If the Cell hasn't already been visited, depth-first search it - if !self.seen.contains(&ptr) { - // Read the cell-pointer - let cell = ptr.read().unwrap(); - // If the cell has an implementation, visit its [Instance]s before inserting it - if let Some(layout) = &cell.layout { - for inst in &layout.insts { - self.push(&inst.cell); - } - } - // And insert the cell (pointer) itself - self.seen.insert(Ptr::clone(ptr)); - self.stack.push(Ptr::clone(ptr)); - } - } -} - -/// Collection of the Views describing a Cell -#[derive(Debug, Default, Clone)] -pub struct Cell { - // Cell Name - pub name: String, - // Layout Abstract - pub abs: Option, - // Layout Implementation - pub layout: Option, -} -impl Cell { - /// Create a new and empty Cell named `name` - pub fn new(name: impl Into) -> Self { - let name = name.into(); - Self { - name, - ..Default::default() - } - } -} -impl From for Cell { - fn from(src: Abstract) -> Self { - Self { - name: src.name.clone(), - abs: Some(src), - ..Default::default() - } - } -} -impl From for Cell { - fn from(src: Layout) -> Self { - Self { - name: src.name.clone(), - layout: Some(src), - ..Default::default() - } - } -} - -/// # Raw-Layout Implementation -/// -/// The geometric-level layout-definition of a [Cell]. -/// Comprised of geometric [Element]s and instances of other [Cell] [Layout]s. -/// -#[derive(Debug, Clone, Default)] -pub struct Layout { - /// Cell Name - pub name: String, - /// Instances - pub insts: Vec, - /// Primitive/ Geometric Elements - pub elems: Vec, - /// Text Annotations - pub annotations: Vec, -} -impl Layout { - /// Create a rectangular [BoundBox] surrounding all elements in the [Layout]. - pub fn bbox(&self) -> BoundBox { - let mut bbox = BoundBox::empty(); - for elem in &self.elems { - bbox = elem.inner.union(&bbox); - } - bbox - } - /// Flatten a [Layout], particularly its hierarchical instances, to a vector of [Element]s - pub fn flatten(&self) -> LayoutResult> { - // Kick off recursive calls, with the identity-transform applied for the top-level `layout` - let mut elems = Vec::new(); - flatten_helper(self, &Transform::identity(), &mut elems)?; - Ok(elems) - } -} -/// Internal helper and core logic for [Layout::flatten]. -fn flatten_helper( - layout: &Layout, - trans: &Transform, - elems: &mut Vec, -) -> LayoutResult<()> { - // Translate each geometric element - for elem in layout.elems.iter() { - // Clone all other data (layer, net, etc.) - // FIXME: hierarchy flattening of net labels - let mut new_elem = elem.clone(); - // And translate the inner shape by `trans` - new_elem.inner = elem.inner.transform(trans); - elems.push(new_elem); - } - // Note text-valued "annotations" are ignored - - // Visit all of `layout`'s instances, recursively getting their elements - for inst in &layout.insts { - // Get the cell's layout-definition, or fail - let cell = inst.cell.read()?; - let layout = cell.layout.as_ref().unwrap(); - - // Create a new [Transform], cascading the parent's and instance's - let inst_trans = Transform::from_instance(&inst.loc, inst.reflect_vert, inst.angle); - let trans = Transform::cascade(&trans, &inst_trans); - - // And recursively add its elements - flatten_helper(&layout, &trans, elems)?; - } - Ok(()) -} - -/// # Text Annotation -/// -/// Note [layout21::raw::TextElement]s are "layer-less", -/// i.e. they do not sit on different layers, -/// and do not describe connectivity or generate pins. -/// These are purely annotations in the sense of "design notes". -/// -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] -pub struct TextElement { - /// String Value - pub string: String, - /// Location - pub loc: Point, -} -/// # Primitive Geometric Element -/// -/// Primary unit of [Layout] definition. -/// Combines a geometric [Shape] with a z-axis [Layer], -/// and optional net connectivity annotation. -/// -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct Element { - /// Net Name - pub net: Option, - /// Layer (Reference) - pub layer: LayerKey, - /// Purpose - pub purpose: LayerPurpose, - /// Shape - pub inner: Shape, -} - -/// Location, orientation, and angular rotation for an [Instance] -/// Note these fields exist "flat" in [Instance] as well, -/// and are grouped here for convenience. -pub struct InstancePlace { - /// Location of `cell` origin - /// regardless of rotation or reflection - pub loc: Point, - /// Vertical reflection, - /// applied *before* rotation - pub reflect_vert: bool, - /// Angle of rotation (degrees), - /// Clockwise and applied *after* reflection - pub angle: Option, -} - -// pub struct Flatten<'l> { -// lib: &'l Library, -// top: &'l Layout, -// } - -// impl Iterator for Flatten { -// type Item = Element; -// fn next(&mut self) -> Option { -// unimplemented!() -// } -// } From 7c01cf3037b84b696f136c6d52ad0ffc2eef8438 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Mon, 31 Jan 2022 23:07:56 -0800 Subject: [PATCH 07/28] Affixing net labels to GDS-borne `Polygon`s. Shape variants `Polygon`, `Rect`, `Path` get first-class structs --- layout21raw/src/bbox.rs | 73 +++---- layout21raw/src/data.rs | 2 +- layout21raw/src/gds.rs | 33 +-- layout21raw/src/geom.rs | 384 +++++++++++++++++++++++---------- layout21raw/src/lef.rs | 35 +-- layout21raw/src/lib.rs | 4 + layout21raw/src/proto.rs | 123 ++++++----- layout21tetris/src/conv/raw.rs | 22 +- 8 files changed, 420 insertions(+), 256 deletions(-) diff --git a/layout21raw/src/bbox.rs b/layout21raw/src/bbox.rs index cb406a0..48b3cd3 100644 --- a/layout21raw/src/bbox.rs +++ b/layout21raw/src/bbox.rs @@ -11,7 +11,7 @@ use crate::{ Int, }; -/// # Rectangular Bounding Box +/// # Axis-Aligned Rectangular Bounding Box /// /// Points `p0` and `p1` represent opposite corners of a bounding rectangle. /// `p0` is always closest to negative-infinity, in both x and y, @@ -79,37 +79,50 @@ impl BoundBox { /// enable geometric transformations such as union and intersection. /// pub trait BoundBoxTrait { + /// Compute a rectangular bounding box around the implementing type. + fn bbox(&self) -> BoundBox; /// Compute the intersection with rectangular bounding box `bbox`. /// Creates and returns a new [BoundBox]. - fn intersection(&self, bbox: &BoundBox) -> BoundBox; + /// Default implementation is to return the intersection of `self.bbox()` and `bbox`. + fn intersection(&self, bbox: &BoundBox) -> BoundBox { + self.bbox().intersection(&bbox) + } /// Compute the union with rectangular bounding box `bbox`. /// Creates and returns a new [BoundBox]. - fn union(&self, bbox: &BoundBox) -> BoundBox; - /// Compute a rectangular bounding box around the implementing type. - fn bbox(&self) -> BoundBox; + /// Default implementation is to return the union of `self.bbox()` and `bbox`. + fn union(&self, bbox: &BoundBox) -> BoundBox { + self.bbox().union(&bbox) + } } - impl BoundBoxTrait for BoundBox { + fn bbox(&self) -> BoundBox { + // We're great as we are, as a [BoundBox] already. + // Create a clone to adhere to our "new bbox" return-type. + self.clone() + } fn intersection(&self, bbox: &BoundBox) -> BoundBox { let pmin = Point::new(self.p0.x.max(bbox.p0.x), self.p0.y.max(bbox.p0.y)); let pmax = Point::new(self.p1.x.min(bbox.p1.x), self.p1.y.min(bbox.p1.y)); + + // Check for empty intersection, and return an empty box if so if pmin.x > pmax.x || pmin.y > pmax.y { return BoundBox::empty(); } + // Otherwise return the intersection BoundBox::new(pmin, pmax) } fn union(&self, bbox: &BoundBox) -> BoundBox { + // Take the minimum and maximum of the two bounding boxes BoundBox::new( Point::new(self.p0.x.min(bbox.p0.x), self.p0.y.min(bbox.p0.y)), Point::new(self.p1.x.max(bbox.p1.x), self.p1.y.max(bbox.p1.y)), ) } - fn bbox(&self) -> BoundBox { - self.clone() - } } - impl BoundBoxTrait for Point { + fn bbox(&self) -> BoundBox { + BoundBox::from_point(self) + } fn intersection(&self, bbox: &BoundBox) -> BoundBox { if !bbox.contains(self) { return BoundBox::empty(); @@ -122,36 +135,24 @@ impl BoundBoxTrait for Point { Point::new(self.x.max(bbox.p1.x), self.y.max(bbox.p1.y)), ) } - fn bbox(&self) -> BoundBox { - BoundBox::from_point(self) - } } - impl BoundBoxTrait for Shape { - fn intersection(&self, bbox: &BoundBox) -> BoundBox { - self.bbox().intersection(&bbox) - } - fn union(&self, bbox: &BoundBox) -> BoundBox { - self.bbox().union(&bbox) - } fn bbox(&self) -> BoundBox { + // Dispatch based on shape-type, either two-Point or multi-Point form. match self { - Shape::Rect { ref p0, ref p1 } => BoundBox::from_points(p0, p1), - Shape::Poly { ref pts } => { - let mut bbox = BoundBox::empty(); - for pt in pts { - bbox = bbox.union(&pt.bbox()); - } - bbox - } - Shape::Path { ref pts, ref width } => { - let mut bbox = BoundBox::empty(); - for pt in pts { - bbox = bbox.union(&pt.bbox()); - } - bbox.expand(*width as Int); - bbox - } + Shape::Rect(ref r) => BoundBox::from_points(&r.p0, &r.p1), + Shape::Polygon(ref p) => (&p.points).bbox(), + Shape::Path(ref p) => (&p.points).bbox(), + } + } +} +impl BoundBoxTrait for Vec { + fn bbox(&self) -> BoundBox { + // Take the union of all points in the vector + let mut bbox = BoundBox::empty(); + for pt in self { + bbox = bbox.union(&pt.bbox()); } + bbox } } diff --git a/layout21raw/src/data.rs b/layout21raw/src/data.rs index 139cd7c..3257a6e 100644 --- a/layout21raw/src/data.rs +++ b/layout21raw/src/data.rs @@ -17,7 +17,7 @@ use slotmap::{new_key_type, SlotMap}; use crate::{ bbox::{BoundBox, BoundBoxTrait}, error::{LayoutError, LayoutResult}, - geom::{Point, Shape, Transform}, + geom::{Point, Shape, Transform, TransformTrait}, utils::{Ptr, PtrList}, }; diff --git a/layout21raw/src/gds.rs b/layout21raw/src/gds.rs index 55094bb..30cd44f 100644 --- a/layout21raw/src/gds.rs +++ b/layout21raw/src/gds.rs @@ -16,7 +16,8 @@ use slotmap::{new_key_type, SlotMap}; use crate::{ utils::{ErrorContext, ErrorHelper, Ptr}, Abstract, AbstractPort, Cell, Dir, Element, Instance, Int, LayerKey, LayerPurpose, Layers, - Layout, LayoutError, LayoutResult, Library, Point, Shape, TextElement, Units, + Layout, LayoutError, LayoutResult, Library, Path, Point, Polygon, Rect, Shape, ShapeTrait, + TextElement, Units, }; pub use gds21; @@ -248,7 +249,8 @@ impl<'lib> GdsExporter<'lib> { layerspec: &gds21::GdsLayerSpec, ) -> LayoutResult { let elem = match shape { - Shape::Rect { p0, p1 } => { + Shape::Rect(r) => { + let (p0, p1) = (&r.p0, &r.p1); let x0 = p0.x.try_into()?; let y0 = p0.y.try_into()?; let x1 = p1.x.try_into()?; @@ -263,14 +265,15 @@ impl<'lib> GdsExporter<'lib> { } .into() } - Shape::Poly { pts } => { + Shape::Polygon(poly) => { // Flatten our points-vec, converting to 32-bit along the way - let mut xy = pts + let mut xy = poly + .points .iter() .map(|p| self.export_point(p)) .collect::, _>>()?; // Add the origin a second time, to "close" the polygon - xy.push(self.export_point(&pts[0])?); + xy.push(self.export_point(&poly.points[0])?); gds21::GdsBoundary { layer: layerspec.layer, datatype: layerspec.xtype, @@ -279,18 +282,18 @@ impl<'lib> GdsExporter<'lib> { } .into() } - Shape::Path { pts, width } => { + Shape::Path(path) => { // Flatten our points-vec, converting to 32-bit along the way let mut xy = Vec::new(); - for p in pts.iter() { + for p in path.points.iter() { xy.push(self.export_point(p)?); } // Add the origin a second time, to "close" the polygon - xy.push(self.export_point(&pts[0])?); + xy.push(self.export_point(&path.points[0])?); gds21::GdsPath { layer: layerspec.layer, datatype: layerspec.xtype, - width: Some(i32::try_from(*width)?), + width: Some(i32::try_from(path.width)?), xy, ..Default::default() } @@ -631,13 +634,13 @@ impl GdsImporter { && pts[3].x == pts[0].x)) { // That makes this a Rectangle. - Shape::Rect { + Shape::Rect(Rect { p0: pts[0].clone(), p1: pts[2].clone(), - } + }) } else { // Otherwise, it's a polygon - Shape::Poly { pts } + Shape::Polygon(Polygon { points: pts }) }; // Grab (or create) its [Layer] @@ -660,10 +663,10 @@ impl GdsImporter { // This does not check fox "box validity", and imports the // first and third of those five coordinates, // which are by necessity for a valid [GdsBox] located at opposite corners. - let inner = Shape::Rect { + let inner = Shape::Rect(Rect { p0: self.import_point(&x.xy[0])?, p1: self.import_point(&x.xy[2])?, - }; + }); // Grab (or create) its [Layer] let (layer, purpose) = self.import_element_layer(x)?; @@ -688,7 +691,7 @@ impl GdsImporter { return self.fail("Invalid nonspecifed GDS Path width "); }; // Create the shape - let inner = Shape::Path { width, pts }; + let inner = Shape::Path(Path { width, points: pts }); // Grab (or create) its [Layer] let (layer, purpose) = self.import_element_layer(x)?; diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs index 3441f0a..5a39c0f 100644 --- a/layout21raw/src/geom.rs +++ b/layout21raw/src/geom.rs @@ -12,7 +12,7 @@ use std::convert::TryFrom; use serde::{Deserialize, Serialize}; // Local imports -use crate::Int; +use crate::{bbox::BoundBoxTrait, Int}; /// # Point in two-dimensional layout-space #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] @@ -90,136 +90,230 @@ impl std::ops::Not for Dir { } } -/// # Shape -/// -/// The primary geometric primitive comprising raw layout. -/// Variants include [Rect], [Polygon], and [Path]. -/// +/// # Path +/// +/// Open-ended geometric path with non-zero width. +/// Primarily consists of a series of ordered [Point]s. +/// +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Path { + pub points: Vec, + pub width: usize, +} +/// # Polygon +/// +/// Closed n-sided polygon with arbitrary number of vertices. +/// Primarily consists of a series of ordered [Point]s. +/// +/// Closure from the last point back to the first is implied; +/// the initial point need not be repeated at the end. +/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Polygon { + pub points: Vec, +} +/// # Rectangle +/// +/// Axis-aligned rectangle, specified by two opposite corners. +/// +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Rect { + pub p0: Point, + pub p1: Point, +} + +/// # Shape +/// +/// The primary geometric primitive comprising raw layout. +/// Variants include [Rect], [Polygon], and [Path]. +/// +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[enum_dispatch(ShapeTrait)] pub enum Shape { - Rect { p0: Point, p1: Point }, - Poly { pts: Vec }, - Path { width: usize, pts: Vec }, + Rect(Rect), + Polygon(Polygon), + Path(Path), } -impl Shape { +#[enum_dispatch] +pub trait ShapeTrait { /// Retrieve our "origin", or first [Point] - pub fn point0(&self) -> &Point { - match *self { - Shape::Rect { ref p0, p1: _ } => p0, - Shape::Poly { ref pts } => &pts[0], - Shape::Path { ref pts, .. } => &pts[0], - } + fn point0(&self) -> &Point; + /// Calculate our center-point + fn center(&self) -> Point; + /// Indicate whether this shape is (more or less) horizontal or vertical + /// Primarily used for orienting label-text + fn orientation(&self) -> Dir; + /// Shift coordinates by the (x,y) values specified in `pt` + fn shift(&mut self, pt: &Point); + /// Boolean indication of whether we contain point `pt` + fn contains(&self, pt: &Point) -> bool; + /// Convert to a [Polygon], our most general of shapes + fn to_poly(&self) -> Polygon; +} +impl ShapeTrait for Rect { + /// Retrieve our "origin", or first [Point] + fn point0(&self) -> &Point { + &self.p0 } /// Calculate our center-point - pub fn center(&self) -> Point { - match *self { - Shape::Rect { ref p0, ref p1 } => Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2), - Shape::Path { ref pts, .. } => { - // Place on the center of the first segment - let p0 = &pts[0]; - let p1 = &pts[1]; - Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) - } - Shape::Poly { .. } => { - unimplemented!("Shape::Poly/Path::center"); - } - } + fn center(&self) -> Point { + let (p0, p1) = (&self.p0, &self.p1); + Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) } /// Indicate whether this shape is (more or less) horizontal or vertical /// Primarily used for orienting label-text - pub fn orientation(&self) -> Dir { - match *self { - Shape::Rect { ref p0, ref p1 } => { - if (p1.x - p0.x).abs() < (p1.y - p0.y).abs() { - return Dir::Vert; - } - Dir::Horiz - } - // Polygon and Path elements always horizontal, at least for now - Shape::Poly { .. } | Shape::Path { .. } => Dir::Horiz, + fn orientation(&self) -> Dir { + let (p0, p1) = (&self.p0, &self.p1); + if (p1.x - p0.x).abs() < (p1.y - p0.y).abs() { + return Dir::Vert; } + Dir::Horiz } - /// Apply matrix-vector [Tranform] `trans`. - /// Creates a new shape at a location equal to the transformation of our own. - pub fn transform(&self, trans: &Transform) -> Shape { - match *self { - Shape::Rect { ref p0, ref p1 } => Shape::Rect { - p0: p0.transform(trans), - p1: p1.transform(trans), - }, - Shape::Poly { ref pts } => Shape::Poly { - pts: pts.iter().map(|p| p.transform(trans)).collect(), - }, - Shape::Path { ref pts, ref width } => Shape::Path { - pts: pts.iter().map(|p| p.transform(trans)).collect(), - width: *width, - }, + /// Shift coordinates by the (x,y) values specified in `pt` + fn shift(&mut self, pt: &Point) { + self.p0.x += pt.x; + self.p0.y += pt.y; + self.p1.x += pt.x; + self.p1.y += pt.y; + } + /// Boolean indication of whether we contain point `pt` + fn contains(&self, pt: &Point) -> bool { + let (p0, p1) = (&self.p0, &self.p1); + p0.x.min(p1.x) <= pt.x + && p0.x.max(p1.x) >= pt.x + && p0.y.min(p1.y) <= pt.y + && p0.y.max(p1.y) >= pt.y + } + fn to_poly(&self) -> Polygon { + let (p0, p1) = (&self.p0, &self.p1); + Polygon { + points: vec![ + p0.clone(), + Point::new(p1.x, p0.y), + p1.clone(), + Point::new(p0.x, p1.y), + ], } } +} +impl ShapeTrait for Polygon { + /// Retrieve our "origin", or first [Point] + fn point0(&self) -> &Point { + &self.points[0] + } + /// Calculate our center-point + fn center(&self) -> Point { + unimplemented!("Poly::center"); + } + /// Indicate whether this shape is (more or less) horizontal or vertical + /// Primarily used for orienting label-text + fn orientation(&self) -> Dir { + // FIXME: always horizontal, at least for now + Dir::Horiz + } /// Shift coordinates by the (x,y) values specified in `pt` - pub fn shift(&mut self, pt: &Point) { - match *self { - Shape::Rect { - ref mut p0, - ref mut p1, - } => { - p0.x += pt.x; - p0.y += pt.y; - p1.x += pt.x; - p1.y += pt.y; - } - Shape::Poly { ref mut pts } => { - for p in pts.iter_mut() { - p.x += pt.x; - p.y += pt.y; - } - } - Shape::Path { ref mut pts, .. } => { - for p in pts.iter_mut() { - p.x += pt.x; - p.y += pt.y; - } - } + fn shift(&mut self, pt: &Point) { + for p in self.points.iter_mut() { + p.x += pt.x; + p.y += pt.y; } } /// Boolean indication of whether we contain point `pt` - pub fn contains(&self, pt: &Point) -> bool { - match self { - Shape::Rect { ref p0, ref p1 } => { - p0.x.min(p1.x) <= pt.x - && p0.x.max(p1.x) >= pt.x - && p0.y.min(p1.y) <= pt.y - && p0.y.max(p1.y) >= pt.y - } - Shape::Poly { .. } => false, // FIXME! todo!(), - Shape::Path { ref width, ref pts } => { - // Break into segments, and check for intersection with each - // Probably not the most efficient way to do this, but a start. - // Only "Manhattan paths", i.e. those with segments solely running vertically or horizontally, are supported. - // FIXME: even with this method, there are some small pieces at corners which we'll miss. - // Whether these are relevant in real life, tbd. - let width = Int::try_from(*width).unwrap(); // FIXME: probably store these signed, check them on creation - for k in 0..pts.len() - 1 { - let rect = if pts[k].x == pts[k + 1].x { - Shape::Rect { - p0: Point::new(pts[k].x - width / 2, pts[k].y), - p1: Point::new(pts[k].x + width / 2, pts[k + 1].y), - } - } else if pts[k].y == pts[k + 1].y { - Shape::Rect { - p0: Point::new(pts[k].x, pts[k].y - width / 2), - p1: Point::new(pts[k + 1].x, pts[k].y + width / 2), - } + fn contains(&self, pt: &Point) -> bool { + // First check for the fast way out: if the point is outside the bounding box, it can't be in the polygon. + if !self.points.bbox().contains(pt) { + return false; + } + // Not quite so lucky this time. Now do some real work. + // Using the "winding number" algorithm, which works for all (real) layout-polygons. + let mut winding_num: isize = 0; + // FIXME: clone and close the polygon + let mut points = self.points.clone(); + points.push(points[0].clone()); + let mut past = &self.points[0]; + for next in &points[1..points.len()] { + // First check whether the segment is in y-range + if past.y.min(next.y) <= pt.y && past.y.max(next.y) >= pt.y { + // May have a hit here. Sort out whether the semi-infinite horizontal line at `y=pt.y` intersects the edge. + let xsolve = (next.x - past.x) * (pt.y - past.y) / (next.y - past.y) + past.x; + + if xsolve >= pt.x { + // We've got a hit on the semi-infinite horizontal line through `pt`. + // Either increment or decrement the winding number. + // FIXME: sort out handling for horizontal edges, i.e. `past.y == next.y` + if next.y > past.y { + winding_num += 1; } else { - unimplemented!("Unsupported Non-Manhattan Path") - }; - if rect.contains(pt) { - return true; + winding_num -= 1; } } - false } + // And update the prior-point + past = next; } + // Trick is: if the winding number is non-zero, we're inside the polygon. + winding_num != 0 + } + fn to_poly(&self) -> Polygon { + self.clone() + } +} +impl ShapeTrait for Path { + /// Retrieve our "origin", or first [Point] + fn point0(&self) -> &Point { + &self.points[0] + } + /// Calculate our center-point + fn center(&self) -> Point { + // Place on the center of the first segment + let p0 = &self.points[0]; + let p1 = &self.points[1]; + Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) + } + /// Indicate whether this shape is (more or less) horizontal or vertical + /// Primarily used for orienting label-text + fn orientation(&self) -> Dir { + // FIXME: always horizontal, at least for now + Dir::Horiz + } + /// Shift coordinates by the (x,y) values specified in `pt` + fn shift(&mut self, pt: &Point) { + for p in self.points.iter_mut() { + p.x += pt.x; + p.y += pt.y; + } + } + /// Boolean indication of whether we contain point `pt` + fn contains(&self, pt: &Point) -> bool { + // Break into segments, and check for intersection with each + // Probably not the most efficient way to do this, but a start. + // Only "Manhattan paths", i.e. those with segments solely running vertically or horizontally, are supported. + // FIXME: even with this method, there are some small pieces at corners which we'll miss. + // Whether these are relevant in real life, tbd. + let (points, width) = (&self.points, self.width); + let width = Int::try_from(width).unwrap(); // FIXME: probably store these signed, check them on creation + for k in 0..points.len() - 1 { + let rect = if points[k].x == points[k + 1].x { + Rect { + p0: Point::new(points[k].x - width / 2, points[k].y), + p1: Point::new(points[k].x + width / 2, points[k + 1].y), + } + } else if points[k].y == points[k + 1].y { + Rect { + p0: Point::new(points[k].x, points[k].y - width / 2), + p1: Point::new(points[k + 1].x, points[k].y + width / 2), + } + } else { + unimplemented!("Unsupported Non-Manhattan Path") + }; + if rect.contains(pt) { + return true; + } + } + false + } + fn to_poly(&self) -> Polygon { + unimplemented!("Path::to_poly") } } @@ -323,50 +417,96 @@ fn matvec(a: &[[f64; 2]; 2], b: &[f64; 2]) -> [f64; 2] { a[1][0] * b[0] + a[1][1] * b[1], ] } +pub trait TransformTrait { + /// Apply matrix-vector [Tranform] `trans`. + /// Creates a new shape at a location equal to the transformation of our own. + fn transform(&self, trans: &Transform) -> Self; +} +impl TransformTrait for Shape { + /// Apply matrix-vector [Tranform] `trans`. + /// Creates a new shape at a location equal to the transformation of our own. + fn transform(&self, trans: &Transform) -> Self { + match self { + Shape::Rect(r) => Shape::Rect(r.transform(trans)), + Shape::Polygon(p) => Shape::Polygon(p.transform(trans)), + Shape::Path(p) => Shape::Path(p.transform(trans)), + } + } +} +impl TransformTrait for Rect { + /// Apply matrix-vector [Tranform] `trans`. + /// Creates a new shape at a location equal to the transformation of our own. + fn transform(&self, trans: &Transform) -> Self { + let (p0, p1) = (&self.p0, &self.p1); + Rect { + p0: p0.transform(trans), + p1: p1.transform(trans), + } + } +} +impl TransformTrait for Polygon { + /// Apply matrix-vector [Tranform] `trans`. + /// Creates a new shape at a location equal to the transformation of our own. + fn transform(&self, trans: &Transform) -> Self { + Polygon { + points: self.points.iter().map(|p| p.transform(trans)).collect(), + } + } +} +impl TransformTrait for Path { + /// Apply matrix-vector [Tranform] `trans`. + /// Creates a new shape at a location equal to the transformation of our own. + fn transform(&self, trans: &Transform) -> Self { + Path { + points: self.points.iter().map(|p| p.transform(trans)).collect(), + width: self.width, + } + } +} #[cfg(test)] pub mod tests { use super::*; #[test] fn transform_identity() { - let shape1 = Shape::Rect { + let shape1 = Shape::Rect(Rect { p0: Point::new(0, 0), p1: Point::new(1, 1), - }; + }); let trans = Transform::identity(); let shape2 = shape1.transform(&trans); assert_eq!(shape2, shape1); } #[test] fn transform_rotate() { - let shape1 = Shape::Rect { + let shape1 = Shape::Rect(Rect { p0: Point::new(0, 0), p1: Point::new(1, 1), - }; + }); let trans = Transform::rotate(90.); let shape2 = shape1.transform(&trans); assert_eq!( shape2, - Shape::Rect { + Shape::Rect(Rect { p0: Point::new(0, 0), p1: Point::new(-1, 1), - } + }) ); let shape3 = shape2.transform(&trans); assert_eq!( shape3, - Shape::Rect { + Shape::Rect(Rect { p0: Point::new(0, 0), p1: Point::new(-1, -1), - } + }) ); let shape4 = shape3.transform(&trans); assert_eq!( shape4, - Shape::Rect { + Shape::Rect(Rect { p0: Point::new(0, 0), p1: Point::new(1, -1), - } + }) ); let shape0 = shape4.transform(&trans); assert_eq!(shape0, shape1); @@ -385,4 +525,10 @@ pub mod tests { let pc1 = p.transform(&cascade2); assert_eq!(pc1, Point::new(2, 0)); } + #[test] + #[ignore] + fn test_shapes_contain() { + // Test shape-point containment of several flavors + todo!(); // FIXME! + } } diff --git a/layout21raw/src/lef.rs b/layout21raw/src/lef.rs index b0e59ee..3f584b3 100644 --- a/layout21raw/src/lef.rs +++ b/layout21raw/src/lef.rs @@ -12,7 +12,7 @@ use std::convert::{TryFrom, TryInto}; use crate::utils::{ErrorContext, ErrorHelper, Ptr}; use crate::{ Abstract, AbstractPort, Cell, Element, Int, Layer, LayerKey, LayerPurpose, Layers, LayoutError, - LayoutResult, Library, Point, Shape, Units, + LayoutResult, Library, Path, Point, Polygon, Rect, Shape, Units, }; use lef21; @@ -133,18 +133,19 @@ impl<'lib> LefExporter<'lib> { fn export_shape(&self, shape: &Shape) -> LayoutResult { // Conver to a [LefShape] let inner: lef21::LefShape = match shape { - Shape::Rect { ref p0, ref p1 } => { - lef21::LefShape::Rect(self.export_point(p0)?, self.export_point(p1)?) + Shape::Rect(ref r) => { + lef21::LefShape::Rect(self.export_point(&r.p0)?, self.export_point(&r.p1)?) } - Shape::Poly { ref pts } => { - let points = pts + Shape::Polygon(ref poly) => { + let points = poly + .points .iter() .map(|p| self.export_point(p)) .collect::, _>>()?; lef21::LefShape::Polygon(points) } Shape::Path { .. } => { - todo!(); + unimplemented!("LefExporter::PATH"); } }; // Wrap it in the [LefGeometry] enum (which also includes repetitions) and return it @@ -274,10 +275,10 @@ impl LefImporter { net: None, layer, purpose: LayerPurpose::Outline, - inner: Shape::Rect { + inner: Shape::Rect(Rect { p0: Point::new(0, 0), p1: lefsize, - }, + }), } }; // Create the [Abstract] to be returned @@ -387,7 +388,7 @@ impl LefImporter { /// Import a [Shape::Poly] fn import_polygon(&mut self, lefpoints: &Vec) -> LayoutResult { let pts: Vec = self.import_point_vec(lefpoints)?; - Ok(Shape::Poly { pts }) + Ok(Shape::Polygon(Polygon { points: pts })) } /// Import a [Shape::Rect] fn import_rect( @@ -396,7 +397,7 @@ impl LefImporter { ) -> LayoutResult { let p0 = self.import_point(lefpoints.0)?; let p1 = self.import_point(lefpoints.1)?; - Ok(Shape::Rect { p0, p1 }) + Ok(Shape::Rect(Rect { p0, p1 })) } /// Import a [Shape::Path] fn import_path( @@ -412,7 +413,7 @@ impl LefImporter { // Convert each of the Points let pts = self.import_point_vec(pts)?; // And return the path - Ok(Shape::Path { width, pts }) + Ok(Shape::Path(Path { width, points: pts })) } /// Import a vector of [Point]s fn import_point_vec(&mut self, pts: &Vec) -> LayoutResult> { @@ -486,20 +487,20 @@ mod tests { net: None, layer: layers.keyname("boundary").unwrap(), purpose: LayerPurpose::Outline, - inner: Shape::Rect { + inner: Shape::Rect(Rect { p0: Point::new(0, 0), p1: Point::new(11, 11), - }, + }), }, ports: vec![AbstractPort { net: "port1".into(), // Collect a hashmap of shapes from (LayerKey, Vec) pairs shapes: vec![( layers.keyname("met1").unwrap(), - vec![Shape::Rect { + vec![Shape::Rect(Rect { p0: Point::new(1, 1), p1: Point::new(2, 2), - }], + })], )] .into_iter() .collect(), @@ -507,10 +508,10 @@ mod tests { // Collect a hashmap of shapes from (LayerKey, Vec) pairs blockages: vec![( layers.keyname("met1").unwrap(), - vec![Shape::Rect { + vec![Shape::Rect(Rect { p0: Point::new(0, 0), p1: Point::new(10, 10), - }], + })], )] .into_iter() .collect(), diff --git a/layout21raw/src/lib.rs b/layout21raw/src/lib.rs index ea12938..b9c2f9d 100644 --- a/layout21raw/src/lib.rs +++ b/layout21raw/src/lib.rs @@ -6,6 +6,10 @@ //! much akin to nearly any legacy layout system. //! +// Crates.io dependencies, at crate-level for their macros +#[macro_use] +extern crate enum_dispatch; + // Internal modules pub mod bbox; pub mod data; diff --git a/layout21raw/src/proto.rs b/layout21raw/src/proto.rs index 2af4b15..d02cb09 100644 --- a/layout21raw/src/proto.rs +++ b/layout21raw/src/proto.rs @@ -17,7 +17,8 @@ use std::convert::{TryFrom, TryInto}; use crate::{ utils::{ErrorContext, ErrorHelper, Ptr}, Abstract, AbstractPort, Cell, DepOrder, Element, Instance, Int, LayerKey, LayerPurpose, Layers, - Layout, LayoutError, LayoutResult, Library, Point, Shape, TextElement, Units, + Layout, LayoutError, LayoutResult, Library, Path, Point, Polygon, Rect, Shape, TextElement, + Units, }; pub use layout21protos as proto; @@ -238,55 +239,62 @@ impl<'lib> ProtoExporter<'lib> { /// Export a [Shape] fn export_shape(&mut self, shape: &Shape) -> LayoutResult { match shape { - Shape::Rect { ref p0, ref p1 } => { - let minx = p0.x.min(p1.x) as i64; - let miny = p0.y.min(p1.y) as i64; - let width = p0.x.max(p1.x) as i64 - minx; - let height = p0.y.max(p1.y) as i64 - miny; - Ok(ProtoShape::Rect(proto::Rectangle { - net: "".into(), - lower_left: Some(proto::Point::new(minx, miny)), - width, - height, - })) - } - Shape::Poly { ref pts } => { - let vertices = pts - .iter() - .map(|p| self.export_point(p)) - .collect::, _>>()?; - Ok(ProtoShape::Poly(proto::Polygon { - net: "".into(), - vertices, - })) - } - Shape::Path { ref width, ref pts } => { - let width = i64::try_from(*width)?; - let points = pts - .iter() - .map(|p| self.export_point(p)) - .collect::, _>>()?; - Ok(ProtoShape::Path(proto::Path { - net: "".into(), - width, - points, - })) - } + Shape::Rect(ref r) => Ok(ProtoShape::Rect(self.export_rect(r)?)), + Shape::Polygon(ref p) => Ok(ProtoShape::Poly(self.export_polygon(p)?)), + Shape::Path(ref p) => Ok(ProtoShape::Path(self.export_path(p)?)), } } + /// Export a [Rect] + fn export_rect(&mut self, rect: &Rect) -> LayoutResult { + let (p0, p1) = (&rect.p0, &rect.p1); + let minx = p0.x.min(p1.x) as i64; + let miny = p0.y.min(p1.y) as i64; + let width = p0.x.max(p1.x) as i64 - minx; + let height = p0.y.max(p1.y) as i64 - miny; + Ok(proto::Rectangle { + net: "".into(), + lower_left: Some(proto::Point::new(minx, miny)), + width, + height, + }) + } + /// Export a [Polygon] + fn export_polygon(&mut self, poly: &Polygon) -> LayoutResult { + let vertices = poly + .points + .iter() + .map(|p| self.export_point(p)) + .collect::, _>>()?; + Ok(proto::Polygon { + net: "".into(), + vertices, + }) + } + /// Export a [Path] + fn export_path(&mut self, path: &Path) -> LayoutResult { + let width = i64::try_from(path.width)?; + let points = path + .points + .iter() + .map(|p| self.export_point(p)) + .collect::, _>>()?; + Ok(proto::Path { + net: "".into(), + width, + points, + }) + } /// Convert `shape` and add it to `pshapes` fn export_and_add_shape( &mut self, shape: &Shape, pshapes: &mut proto::LayerShapes, ) -> LayoutResult<()> { - // Convert the shape - let pshape = self.export_shape(shape)?; - // And add it to the appropriate list - match pshape { - ProtoShape::Rect(rect) => pshapes.rectangles.push(rect), - ProtoShape::Poly(poly) => pshapes.polygons.push(poly), - ProtoShape::Path(path) => pshapes.paths.push(path), + // Convert the shape, and add it to the appropriate list + match shape { + Shape::Rect(rect) => pshapes.rectangles.push(self.export_rect(rect)?), + Shape::Polygon(poly) => pshapes.polygons.push(self.export_polygon(poly)?), + Shape::Path(path) => pshapes.paths.push(self.export_path(path)?), }; Ok(()) } @@ -486,8 +494,8 @@ impl ProtoImporter { } /// Import a [Shape::Poly] fn import_polygon(&mut self, ppoly: &proto::Polygon) -> LayoutResult { - let pts: Vec = self.import_point_vec(&ppoly.vertices)?; - Ok(Shape::Poly { pts }) + let points: Vec = self.import_point_vec(&ppoly.vertices)?; + Ok(Shape::Polygon(Polygon { points })) } /// Import a [Shape::Rect] fn import_rect(&mut self, prect: &proto::Rectangle) -> LayoutResult { @@ -498,13 +506,13 @@ impl ProtoImporter { let width = Int::try_from(prect.width)?; let height = Int::try_from(prect.height)?; let p1 = Point::new(p0.x + width, p0.y + height); - Ok(Shape::Rect { p0, p1 }) + Ok(Shape::Rect(Rect { p0, p1 })) } /// Import a [Shape::Path] fn import_path(&mut self, x: &proto::Path) -> LayoutResult { - let pts = self.import_point_vec(&x.points)?; + let points = self.import_point_vec(&x.points)?; let width = usize::try_from(x.width)?; - Ok(Shape::Path { width, pts }) + Ok(Shape::Path(Path { width, points })) } /// Add the finishing touches to convert a [Shape] to an [Element] fn convert_shape( @@ -586,8 +594,9 @@ impl ProtoImporter { Ok(Point::new(x, y)) } /// Import a vector of [Point]s - fn import_point_vec(&mut self, pts: &Vec) -> LayoutResult> { - pts.iter() + fn import_point_vec(&mut self, points: &Vec) -> LayoutResult> { + points + .iter() .map(|p| self.import_point(p)) .collect::, _>>() } @@ -628,27 +637,27 @@ fn proto1() -> LayoutResult<()> { net: Some("prt_rect_net".to_string()), layer, purpose: purpose.clone(), - inner: Shape::Rect { + inner: Shape::Rect(Rect { p0: Point::default(), p1: Point::default(), - }, + }), }, Element { net: Some("prt_poly_net".to_string()), layer, purpose: purpose.clone(), - inner: Shape::Poly { - pts: vec![Point::default(), Point::default(), Point::default()], - }, + inner: Shape::Polygon(Polygon { + points: vec![Point::default(), Point::default(), Point::default()], + }), }, Element { net: Some("prt_path_net".to_string()), layer, purpose: purpose.clone(), - inner: Shape::Path { + inner: Shape::Path(Path { width: 5, - pts: vec![Point::default(), Point::default(), Point::default()], - }, + points: vec![Point::default(), Point::default(), Point::default()], + }), }, ], insts: Vec::new(), diff --git a/layout21tetris/src/conv/raw.rs b/layout21tetris/src/conv/raw.rs index fa49391..24daac2 100644 --- a/layout21tetris/src/conv/raw.rs +++ b/layout21tetris/src/conv/raw.rs @@ -381,7 +381,7 @@ impl<'lib> RawExporter { net: Some(assn.src.net.clone()), layer: via_layer.raw.unwrap(), purpose: raw::LayerPurpose::Drawing, - inner: raw::Shape::Rect { + inner: raw::Shape::Rect(raw::Rect { p0: self.export_point( assn_loc.x - via_layer.size.x / 2, assn_loc.y - via_layer.size.y / 2, @@ -390,7 +390,7 @@ impl<'lib> RawExporter { assn_loc.x + via_layer.size.x / 2, assn_loc.y + via_layer.size.y / 2, ), - }, + }), }; elems.push(e); } @@ -495,10 +495,10 @@ impl<'lib> RawExporter { } ( self.stack.metal(*layer_index)?.raw.unwrap(), - raw::Shape::Rect { + raw::Shape::Rect(raw::Rect { p0: self.export_xy(&pts[0]), p1: self.export_xy(&pts[1]), - }, + }), ) } ZTopEdge { track, side, into } => { @@ -534,10 +534,10 @@ impl<'lib> RawExporter { } ( self.stack.metal(top_metal)?.raw.unwrap(), - raw::Shape::Rect { + raw::Shape::Rect(raw::Rect { p0: self.export_xy(&pts[0]), p1: self.export_xy(&pts[1]), - }, + }), ) } ZTopInner { .. } => todo!(), @@ -585,7 +585,7 @@ impl<'lib> RawExporter { } // Add the final implied Point at (x, y[-1]) pts.push(Point::new(0, yp)); - Ok(raw::Shape::Poly { pts }) + Ok(raw::Shape::Polygon(raw::Polygon { points: pts })) } /// Convert an [Outline] to a [raw::Element] polygon pub fn export_outline(&self, outline: &Outline) -> LayoutResult { @@ -616,14 +616,14 @@ impl<'lib> RawExporter { }; // Convert the inner shape let inner = match track.data.dir { - Dir::Horiz => raw::Shape::Rect { + Dir::Horiz => raw::Shape::Rect(raw::Rect { p0: self.export_point(seg.start, track.data.start), p1: self.export_point(seg.stop, track.data.start + track.data.width), - }, - Dir::Vert => raw::Shape::Rect { + }), + Dir::Vert => raw::Shape::Rect(raw::Rect { p0: self.export_point(track.data.start, seg.start), p1: self.export_point(track.data.start + track.data.width, seg.stop), - }, + }), }; // And pack it up as a [raw::Element] let e = raw::Element { From d3e2e73dc0b5de340ced067838ee892658f90424 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 1 Feb 2022 13:19:38 -0800 Subject: [PATCH 08/28] Unit tests and debug of `Polygon::contains` --- layout21raw/src/geom.rs | 151 ++++++++++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 36 deletions(-) diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs index 5a39c0f..d8f7e25 100644 --- a/layout21raw/src/geom.rs +++ b/layout21raw/src/geom.rs @@ -140,12 +140,14 @@ pub trait ShapeTrait { fn point0(&self) -> &Point; /// Calculate our center-point fn center(&self) -> Point; - /// Indicate whether this shape is (more or less) horizontal or vertical - /// Primarily used for orienting label-text + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir; /// Shift coordinates by the (x,y) values specified in `pt` fn shift(&mut self, pt: &Point); - /// Boolean indication of whether we contain point `pt` + /// Boolean indication of whether the [Shape] contains [Point] `pt`. + /// Containment is *inclusive* for all [Shape] types. + /// [Point]s on their boundary, which generally include all points specifying the shape itself, are regarded throughout as "inside" the shape. fn contains(&self, pt: &Point) -> bool; /// Convert to a [Polygon], our most general of shapes fn to_poly(&self) -> Polygon; @@ -160,8 +162,8 @@ impl ShapeTrait for Rect { let (p0, p1) = (&self.p0, &self.p1); Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) } - /// Indicate whether this shape is (more or less) horizontal or vertical - /// Primarily used for orienting label-text + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir { let (p0, p1) = (&self.p0, &self.p1); if (p1.x - p0.x).abs() < (p1.y - p0.y).abs() { @@ -176,7 +178,9 @@ impl ShapeTrait for Rect { self.p1.x += pt.x; self.p1.y += pt.y; } - /// Boolean indication of whether we contain point `pt` + /// Boolean indication of whether the [Shape] contains [Point] `pt`. + /// Containment is *inclusive* for all [Shape] types. + /// [Point]s on their boundary, which generally include all points specifying the shape itself, are regarded throughout as "inside" the shape. fn contains(&self, pt: &Point) -> bool { let (p0, p1) = (&self.p0, &self.p1); p0.x.min(p1.x) <= pt.x @@ -205,8 +209,8 @@ impl ShapeTrait for Polygon { fn center(&self) -> Point { unimplemented!("Poly::center"); } - /// Indicate whether this shape is (more or less) horizontal or vertical - /// Primarily used for orienting label-text + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir { // FIXME: always horizontal, at least for now Dir::Horiz @@ -218,40 +222,56 @@ impl ShapeTrait for Polygon { p.y += pt.y; } } - /// Boolean indication of whether we contain point `pt` + /// Boolean indication of whether the [Shape] contains [Point] `pt`. + /// Containment is *inclusive* for all [Shape] types. + /// [Point]s on their boundary, which generally include all points specifying the shape itself, are regarded throughout as "inside" the shape. fn contains(&self, pt: &Point) -> bool { // First check for the fast way out: if the point is outside the bounding box, it can't be in the polygon. if !self.points.bbox().contains(pt) { return false; } - // Not quite so lucky this time. Now do some real work. - // Using the "winding number" algorithm, which works for all (real) layout-polygons. + + // Not quite so lucky this time. Now do some real work. Using the "winding number" algorithm, which works for all (realistically useful) layout-polygons. let mut winding_num: isize = 0; - // FIXME: clone and close the polygon - let mut points = self.points.clone(); - points.push(points[0].clone()); - let mut past = &self.points[0]; - for next in &points[1..points.len()] { - // First check whether the segment is in y-range + for idx in 0..self.points.len() { + // Grab the segment's start and end points. + // Note these accesses go one past `points.len`, closing the polygon back at its first point. + let (past, next) = ( + &self.points[idx], + &self.points[(idx + 1) % self.points.len()], + ); + + // First check whether the point is anywhere in the y-range of this segment if past.y.min(next.y) <= pt.y && past.y.max(next.y) >= pt.y { // May have a hit here. Sort out whether the semi-infinite horizontal line at `y=pt.y` intersects the edge. - let xsolve = (next.x - past.x) * (pt.y - past.y) / (next.y - past.y) + past.x; + if next.y == past.y { + // This is a horizontal segment, and we're on the same y-level as the point. + // If its x-coordinate also lies within range, no need for further checks, we've got a hit. + if past.x.min(next.x) <= pt.x && past.x.max(next.x) >= pt.x { + return true; + } + // Otherwise "hits" against these horizontal segments are not counted in `winding_num`. + // (FIXME: double-check this.) + } else { + // This is a non-horizontal segment. Check for intersection. + let xsolve = (next.x - past.x) * (pt.y - past.y) / (next.y - past.y) + past.x; - if xsolve >= pt.x { - // We've got a hit on the semi-infinite horizontal line through `pt`. - // Either increment or decrement the winding number. - // FIXME: sort out handling for horizontal edges, i.e. `past.y == next.y` - if next.y > past.y { - winding_num += 1; - } else { - winding_num -= 1; + if xsolve == pt.x { + // This segment runs straight through the point. No need to check further. + return true; + } else if xsolve > pt.x { + // We've got a hit on the semi-infinite horizontal line through `pt`. + // Either increment or decrement the winding number. + if next.y > past.y { + winding_num += 1; + } else { + winding_num -= 1; + } } } } - // And update the prior-point - past = next; } - // Trick is: if the winding number is non-zero, we're inside the polygon. + // Trick is: if the winding number is non-zero, we're inside the polygon. And if it's zero, we're outside. winding_num != 0 } fn to_poly(&self) -> Polygon { @@ -270,8 +290,8 @@ impl ShapeTrait for Path { let p1 = &self.points[1]; Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) } - /// Indicate whether this shape is (more or less) horizontal or vertical - /// Primarily used for orienting label-text + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir { // FIXME: always horizontal, at least for now Dir::Horiz @@ -283,7 +303,9 @@ impl ShapeTrait for Path { p.y += pt.y; } } - /// Boolean indication of whether we contain point `pt` + /// Boolean indication of whether the [Shape] contains [Point] `pt`. + /// Containment is *inclusive* for all [Shape] types. + /// [Point]s on their boundary, which generally include all points specifying the shape itself, are regarded throughout as "inside" the shape. fn contains(&self, pt: &Point) -> bool { // Break into segments, and check for intersection with each // Probably not the most efficient way to do this, but a start. @@ -526,9 +548,66 @@ pub mod tests { assert_eq!(pc1, Point::new(2, 0)); } #[test] - #[ignore] - fn test_shapes_contain() { - // Test shape-point containment of several flavors - todo!(); // FIXME! + fn test_polygon_contains() { + // Test polygon-point containment of several flavors + + // Create a right triangle at the origin + let triangle = Polygon { + points: vec![Point::new(0, 0), Point::new(2, 0), Point::new(0, 2)], + }; + assert!(triangle.contains(&Point::new(0, 0))); + assert!(triangle.contains(&Point::new(1, 0))); + assert!(triangle.contains(&Point::new(2, 0))); + assert!(triangle.contains(&Point::new(0, 1))); + assert!(triangle.contains(&Point::new(1, 1))); + assert!(!triangle.contains(&Point::new(2, 2))); + + // Create a 2:1 tall-ish diamond-shape + let diamond = Polygon { + points: vec![ + Point::new(1, 0), + Point::new(2, 2), + Point::new(1, 4), + Point::new(0, 2), + ], + }; + assert!(!diamond.contains(&Point::new(0, 0))); + assert!(!diamond.contains(&Point::new(100, 100))); + // Check a few points through its vertical center + assert!(diamond.contains(&Point::new(1, 0))); + assert!(diamond.contains(&Point::new(1, 1))); + assert!(diamond.contains(&Point::new(1, 2))); + assert!(diamond.contains(&Point::new(1, 3))); + assert!(diamond.contains(&Point::new(1, 4))); + // And its horizontal center + assert!(diamond.contains(&Point::new(0, 2))); + assert!(diamond.contains(&Point::new(1, 2))); + assert!(diamond.contains(&Point::new(2, 2))); + + // More fun: create a U-shaped polygon, inside a 10x10 square + let u = Polygon { + points: vec![ + Point::new(0, 0), + Point::new(0, 10), + Point::new(2, 10), + Point::new(2, 2), + Point::new(8, 2), + Point::new(8, 10), + Point::new(10, 10), + Point::new(10, 0), + ], + }; + for pt in &u.points { + assert!(u.contains(pt)); + } + assert!(u.contains(&Point::new(1, 1))); + assert!(u.contains(&Point::new(1, 9))); + assert!(u.contains(&Point::new(9, 9))); + assert!(u.contains(&Point::new(9, 1))); + // Points "inside" the u-part, i.e. "outside" the polygon + assert!(!u.contains(&Point::new(3, 3))); + assert!(!u.contains(&Point::new(3, 9))); + assert!(!u.contains(&Point::new(7, 3))); + assert!(!u.contains(&Point::new(7, 9))); } } From 248e49db24dcea977e1f3bcce133c9c463e4e811 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 1 Feb 2022 13:58:17 -0800 Subject: [PATCH 09/28] Speed up `gds21::scan` test, some --- gds21/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gds21/src/tests.rs b/gds21/src/tests.rs index d8c8f7e..15200b8 100644 --- a/gds21/src/tests.rs +++ b/gds21/src/tests.rs @@ -54,7 +54,7 @@ fn stats() -> GdsResult<()> { #[test] fn scan() -> GdsResult<()> { // Test first-pass scanning - let fname = resource("has_properties.gds"); + let fname = resource("sample1.gds"); let _scan = GdsScanner::scan(&fname)?; Ok(()) } From b96c0628e1a5c9cf8eeb1a6a06c8c259be53aed1 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 1 Feb 2022 13:58:59 -0800 Subject: [PATCH 10/28] Verify and update new golden data for `gds_to_proto1` test. Add YAML-format version --- .../resources/dff1_lib.golden.vlsir.bin | Bin 9773 -> 9805 bytes .../resources/dff1_lib.golden.vlsir.yaml | 2914 +++++++++++++++++ layout21raw/src/tests.rs | 25 +- 3 files changed, 2929 insertions(+), 10 deletions(-) create mode 100644 layout21raw/resources/dff1_lib.golden.vlsir.yaml diff --git a/layout21raw/resources/dff1_lib.golden.vlsir.bin b/layout21raw/resources/dff1_lib.golden.vlsir.bin index 34e84eab7b56240d8b7ce146bbc432cfa13d3c0a..535c19e1f437811495dcdc09477ae82c6830132e 100644 GIT binary patch delta 113 zcmZ4MbJj delta 81 zcmX@>v(`tPiz6j1%`iSEGf99kXr2!j3y7OFb)$GJKjZ1mrTqMiOf#4^_XtQbGR LayoutResult<()> { Ok(()) } -/// Grab the full path of resource-file `fname` -fn resource(rname: &str) -> String { - format!("{}/resources/{}", env!("CARGO_MANIFEST_DIR"), rname) -} + /// Take a trip through GDSII -> Layout21::Raw -> ProtoBuf #[cfg(all(feature = "gds", feature = "proto"))] #[test] fn test_gds_to_proto1() -> LayoutResult<()> { + use crate::{ + gds::gds21::GdsLibrary, + proto::proto::{Library as ProtoLibrary, ProtoFile}, + }; // Read a GDS file let samp = resource("dff1_lib.golden.gds"); - let gds = gds::gds21::GdsLibrary::load(&samp)?; + let gds = GdsLibrary::load(&samp)?; // Convert to Layout21::Raw - let lib = gds::GdsImporter::import(&gds, None)?; + let lib = Library::from_gds(&gds, None)?; assert_eq!(lib.name, "dff1_lib"); assert_eq!(lib.cells.len(), 1); @@ -83,11 +84,15 @@ fn test_gds_to_proto1() -> LayoutResult<()> { assert_eq!(cell.name, "dff1"); // Convert to ProtoBuf - let p = proto::ProtoExporter::export(&lib)?; - assert_eq!(p.domain, "dff1_lib"); + let plib: ProtoLibrary = lib.to_proto()?; + assert_eq!(plib.domain, "dff1_lib"); // And compare against the golden version - let p2 = proto::proto::open(&resource("dff1_lib.golden.vlsir.bin")).unwrap(); - assert_eq!(p, p2); + let plib2 = ProtoLibrary::open(&resource("dff1_lib.golden.vlsir.bin")).unwrap(); + assert_eq!(plib, plib2); Ok(()) } +/// Grab the full path of resource-file `fname` +fn resource(rname: &str) -> String { + format!("{}/resources/{}", env!("CARGO_MANIFEST_DIR"), rname) +} From 29ed74358efa7f57f4eb66370e8db77145242666 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 1 Feb 2022 16:01:26 -0800 Subject: [PATCH 11/28] Raw -> GDS export adds `PlaceLabels`, particularly its implementation for `Polygon` --- layout21raw/src/bbox.rs | 13 ++++--- layout21raw/src/gds.rs | 82 +++++++++++++++++++++++++++++++++++++++-- layout21raw/src/geom.rs | 60 ++++++++++++++---------------- 3 files changed, 114 insertions(+), 41 deletions(-) diff --git a/layout21raw/src/bbox.rs b/layout21raw/src/bbox.rs index 48b3cd3..a36a61e 100644 --- a/layout21raw/src/bbox.rs +++ b/layout21raw/src/bbox.rs @@ -69,6 +69,10 @@ impl BoundBox { pub fn size(&self) -> (Int, Int) { (self.p1.x - self.p0.x, self.p1.y - self.p0.y) } + /// Get the box's center + pub fn center(&self) -> Point { + Point::new((self.p0.x + self.p1.x) / 2, (self.p0.y + self.p1.y) / 2) + } } /// @@ -97,13 +101,12 @@ pub trait BoundBoxTrait { impl BoundBoxTrait for BoundBox { fn bbox(&self) -> BoundBox { // We're great as we are, as a [BoundBox] already. - // Create a clone to adhere to our "new bbox" return-type. - self.clone() + // Create a clone to adhere to our "new bbox" return-type. + self.clone() } fn intersection(&self, bbox: &BoundBox) -> BoundBox { let pmin = Point::new(self.p0.x.max(bbox.p0.x), self.p0.y.max(bbox.p0.y)); let pmax = Point::new(self.p1.x.min(bbox.p1.x), self.p1.y.min(bbox.p1.y)); - // Check for empty intersection, and return an empty box if so if pmin.x > pmax.x || pmin.y > pmax.y { return BoundBox::empty(); @@ -127,7 +130,7 @@ impl BoundBoxTrait for Point { if !bbox.contains(self) { return BoundBox::empty(); } - BoundBox::from_point(self) + bbox.intersection(&BoundBox::from_point(self)) } fn union(&self, bbox: &BoundBox) -> BoundBox { BoundBox::new( @@ -138,7 +141,7 @@ impl BoundBoxTrait for Point { } impl BoundBoxTrait for Shape { fn bbox(&self) -> BoundBox { - // Dispatch based on shape-type, either two-Point or multi-Point form. + // Dispatch based on shape-type, either two-Point or multi-Point form. match self { Shape::Rect(ref r) => BoundBox::from_points(&r.p0, &r.p1), Shape::Polygon(ref p) => (&p.points).bbox(), diff --git a/layout21raw/src/gds.rs b/layout21raw/src/gds.rs index 30cd44f..ce581ce 100644 --- a/layout21raw/src/gds.rs +++ b/layout21raw/src/gds.rs @@ -14,10 +14,12 @@ use slotmap::{new_key_type, SlotMap}; // Local imports use crate::{ + bbox::BoundBoxTrait, + error::{LayoutError, LayoutResult}, + geom::{Path, Point, Polygon, Rect, Shape, ShapeTrait}, utils::{ErrorContext, ErrorHelper, Ptr}, Abstract, AbstractPort, Cell, Dir, Element, Instance, Int, LayerKey, LayerPurpose, Layers, - Layout, LayoutError, LayoutResult, Library, Path, Point, Polygon, Rect, Shape, ShapeTrait, - TextElement, Units, + Layout, Library, TextElement, Units, }; pub use gds21; @@ -309,8 +311,9 @@ impl<'lib> GdsExporter<'lib> { shape: &Shape, layerspec: &gds21::GdsLayerSpec, ) -> LayoutResult { - // Text is placed in the shape's (at least rough) center - let loc = shape.center(); + // Sort out a location to place the text + let loc = shape.label_location()?; + // Rotate that text 90 degrees for mostly-vertical shapes let strans = match shape.orientation() { Dir::Horiz => None, @@ -347,6 +350,77 @@ impl ErrorHelper for GdsExporter<'_> { } } +/// # PlaceLabels +/// +/// Trait for calculating the location of text-labels, generally per [Shape]. +/// +/// Sole function `label_location` calculates an appropriate location, +/// or returns a [LayoutError] if one cannot be found. +/// +/// While Layout21 formats do not include "placed text", GDSII relies on it for connectivity annotations. +/// How to place these labels varies by shape type. +/// +trait PlaceLabels { + fn label_location(&self) -> LayoutResult; +} +impl PlaceLabels for Shape { + fn label_location(&self) -> LayoutResult { + // Dispatch based on shape-type + match self { + Shape::Rect(ref r) => r.label_location(), + Shape::Polygon(ref p) => p.label_location(), + Shape::Path(ref p) => p.label_location(), + } + } +} +impl PlaceLabels for Rect { + fn label_location(&self) -> LayoutResult { + // Place rectangle-labels in the center of the rectangle + Ok(self.center()) + } +} +impl PlaceLabels for Path { + fn label_location(&self) -> LayoutResult { + // Place on the center of the first segment + let p0 = &self.points[0]; + let p1 = &self.points[1]; + Ok(Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2)) + } +} +impl PlaceLabels for Polygon { + fn label_location(&self) -> LayoutResult { + // Where, oh where, to place a label on an arbitrary polygon? Let us count the ways. + + // Priority 1: if the center of our bounding box lies within the polygon, use that. + // In simple-polygon cases, this is most likely our best choice. + // In many other cases, e.g. for "U-shaped" polygons, this will fall outside the polygon and be invalid. + let bbox_center = self.points.bbox().center(); + if self.contains(&bbox_center) { + return Ok(bbox_center); + } + + // Priority 2: try the four coordinates immediately above, below, left, and right of the polygon's first point. + // At least one must lie within the polygon for it to be a valid layout shape. + // If none are, fail. + let pt0 = self.point0(); + let candidates = vec![ + Point::new(pt0.x, pt0.y - 1), + Point::new(pt0.x - 1, pt0.y), + Point::new(pt0.x, pt0.y + 1), + Point::new(pt0.x + 1, pt0.y), + ]; + for pt in candidates { + if self.contains(&pt) { + return Ok(pt); + } + } + Err(LayoutError::msg(format!( + "No valid label location found for polygon {:?}", + self, + ))) + } +} + /// # Gds Dependency-Order /// /// Creates a vector of references Gds structs, ordered by their instance dependencies. diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs index d8f7e25..b90718b 100644 --- a/layout21raw/src/geom.rs +++ b/layout21raw/src/geom.rs @@ -12,7 +12,10 @@ use std::convert::TryFrom; use serde::{Deserialize, Serialize}; // Local imports -use crate::{bbox::BoundBoxTrait, Int}; +use crate::{ + bbox::BoundBoxTrait, + Int, +}; /// # Point in two-dimensional layout-space #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] @@ -121,6 +124,12 @@ pub struct Rect { pub p0: Point, pub p1: Point, } +impl Rect { + /// Calculate our center-point + pub fn center(&self) -> Point { + Point::new((self.p0.x + self.p1.x) / 2, (self.p0.y + self.p1.y) / 2) + } +} /// # Shape /// @@ -134,14 +143,16 @@ pub enum Shape { Polygon(Polygon), Path(Path), } +/// # ShapeTrait +/// +/// Common shape operations, dispatched from the [Shape] enum to its variants by [enum_dispatch]. +/// #[enum_dispatch] pub trait ShapeTrait { /// Retrieve our "origin", or first [Point] fn point0(&self) -> &Point; - /// Calculate our center-point - fn center(&self) -> Point; - /// Indicate whether this shape is (more or less) horizontal or vertical. - /// Primarily used for orienting label-text. + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir; /// Shift coordinates by the (x,y) values specified in `pt` fn shift(&mut self, pt: &Point); @@ -157,13 +168,9 @@ impl ShapeTrait for Rect { fn point0(&self) -> &Point { &self.p0 } - /// Calculate our center-point - fn center(&self) -> Point { - let (p0, p1) = (&self.p0, &self.p1); - Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) - } - /// Indicate whether this shape is (more or less) horizontal or vertical. - /// Primarily used for orienting label-text. + + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir { let (p0, p1) = (&self.p0, &self.p1); if (p1.x - p0.x).abs() < (p1.y - p0.y).abs() { @@ -189,13 +196,13 @@ impl ShapeTrait for Rect { && p0.y.max(p1.y) >= pt.y } fn to_poly(&self) -> Polygon { - let (p0, p1) = (&self.p0, &self.p1); + // Create a four-sided polygon, cloning our corners Polygon { points: vec![ - p0.clone(), - Point::new(p1.x, p0.y), - p1.clone(), - Point::new(p0.x, p1.y), + self.p0.clone(), + Point::new(self.p1.x, self.p0.y), + self.p1.clone(), + Point::new(self.p0.x, self.p1.y), ], } } @@ -205,12 +212,8 @@ impl ShapeTrait for Polygon { fn point0(&self) -> &Point { &self.points[0] } - /// Calculate our center-point - fn center(&self) -> Point { - unimplemented!("Poly::center"); - } - /// Indicate whether this shape is (more or less) horizontal or vertical. - /// Primarily used for orienting label-text. + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir { // FIXME: always horizontal, at least for now Dir::Horiz @@ -283,15 +286,8 @@ impl ShapeTrait for Path { fn point0(&self) -> &Point { &self.points[0] } - /// Calculate our center-point - fn center(&self) -> Point { - // Place on the center of the first segment - let p0 = &self.points[0]; - let p1 = &self.points[1]; - Point::new((p0.x + p1.x) / 2, (p0.y + p1.y) / 2) - } - /// Indicate whether this shape is (more or less) horizontal or vertical. - /// Primarily used for orienting label-text. + /// Indicate whether this shape is (more or less) horizontal or vertical. + /// Primarily used for orienting label-text. fn orientation(&self) -> Dir { // FIXME: always horizontal, at least for now Dir::Horiz From 213fdfbe4933594caee3778c69bfe8614933e89c Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Mon, 14 Feb 2022 10:21:43 -0800 Subject: [PATCH 12/28] WIP Device Extraction --- layout21raw/src/geom.rs | 13 ++-- rules21/Cargo.toml | 1 + rules21/src/data.rs | 155 ++++++++++++++++++++++++++++++++++------ 3 files changed, 142 insertions(+), 27 deletions(-) diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs index b90718b..13777db 100644 --- a/layout21raw/src/geom.rs +++ b/layout21raw/src/geom.rs @@ -12,10 +12,7 @@ use std::convert::TryFrom; use serde::{Deserialize, Serialize}; // Local imports -use crate::{ - bbox::BoundBoxTrait, - Int, -}; +use crate::{bbox::BoundBoxTrait, Int}; /// # Point in two-dimensional layout-space #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] @@ -143,6 +140,13 @@ pub enum Shape { Polygon(Polygon), Path(Path), } +impl Shape { + /// Boolean indication of whether we intersect with [Shape] `other`. + pub fn intersects(&self, other: &Shape) -> bool { + todo!() // FIXME! + } +} + /// # ShapeTrait /// /// Common shape operations, dispatched from the [Shape] enum to its variants by [enum_dispatch]. @@ -163,6 +167,7 @@ pub trait ShapeTrait { /// Convert to a [Polygon], our most general of shapes fn to_poly(&self) -> Polygon; } + impl ShapeTrait for Rect { /// Retrieve our "origin", or first [Point] fn point0(&self) -> &Point { diff --git a/rules21/Cargo.toml b/rules21/Cargo.toml index 7fb38c8..e80c11f 100644 --- a/rules21/Cargo.toml +++ b/rules21/Cargo.toml @@ -16,6 +16,7 @@ layout21raw = {path = "../layout21raw", version = "0.2.1"} layout21utils = {path = "../layout21utils", version = "0.2.1"} # External dependencies +slotmap = {version = "1.0", features = ["serde"]} # derive_builder = "0.9.0" # enum_dispatch = "0.3.1" # num-derive = "0.3" diff --git a/rules21/src/data.rs b/rules21/src/data.rs index ce1916a..f772dd5 100644 --- a/rules21/src/data.rs +++ b/rules21/src/data.rs @@ -1,9 +1,24 @@ -use std::collections::HashMap; +//! +//! # Layout Rules Data Model +//! +//! Defines the core types for connectivity and validity of raw-geometry-style layout. +//! + +// Std-library imports +use std::collections::{HashMap, HashSet}; + +// Crates.io +use slotmap::{new_key_type, SlotMap}; // Local Imports use layout21raw as raw; use layout21utils as utils; -use utils::ptr::Ptr; +use utils::ptr::{Ptr, PtrList}; + +// Create key-types for each internal type stored in [SlotMap]s +new_key_type! { + pub struct ElementKey; +} /// # Rules for Circuit-Extraction from a Layout /// @@ -12,35 +27,129 @@ use utils::ptr::Ptr; /// pub struct CircuitExtractionRules { pub layers: Vec, - pub devices: Vec, + pub devices: PtrList, pub connect_rules: Vec, } +pub struct LayerElements { + pub layer: Ptr, + pub elements: Vec, +} +#[derive(Debug, Default)] +struct FlatLayout { + pub net_names: HashSet, + pub by_net: HashMap>, + pub by_layer: HashMap<(raw::LayerKey, raw::LayerPurpose), Vec>, + pub elements: SlotMap, +} +impl FlatLayout { + pub fn from_raw(layout: &raw::Layout) -> Self { + let mut this = Self::default(); + + // Flatten any hierarchy in the input layout, creating a flat vector of shape-elements + let elements = layout.flatten().unwrap(); + for element in elements { + // Add the element to our slot-mapped set, and get a key for it + let ekey = this.elements.insert(element); + let element = &this.elements[ekey]; + + // If the element has a labeled net, add it to the by-net set + if let Some(ref net_name) = element.net { + this.net_names.insert(net_name.to_string()); + this.by_net + .entry(net_name.to_string()) + .or_insert_with(Vec::new) + .push(ekey); + } + // And add it to the by-layer set + this.by_layer + .entry((element.layer, element.purpose.clone())) + .or_insert_with(Vec::new) + .push(ekey); + } + this + } +} + fn extract_instances( layout: &raw::Layout, rules: &CircuitExtractionRules, -) -> Result, ()> { - // // Flatten `layout`, replacing all layout-instances with raw shapes - // let flat = flatten(layout); - // // Associate each shape in `layout` with one of `rules` layers, or fail +) -> Result, Vec>, ()> { + // Flatten `layout`, replacing all layout-instances with raw shapes. + // This also gives us ownership of the flattened shapes. + let layout = FlatLayout::from_raw(layout); + + // Associate each shape in `layout` with one of `rules` layers, or fail // map_some_layer_thing(layout_layers, rules.layers); - // let mut instances = Vec::new(); - // // For each device-definition in `rules`, search each shape in `layout` for a match - // for device in rules.devices.iter() { - // for id_layer_shape in shapes[device.id_layer.read()].iter() { - // for port_layer in device.ports.iter() { - // for port_layer_shape in shapes[port_layer.read()].iter() { - // if everything_overlaps { - // instances.push(Instance::new(/* stuff */)); - // } - // } - // } - // } - // } + // For each device-definition in `rules`, search each shape in `layout` for a match + let mut instances: HashMap, Vec> = HashMap::new(); + for device in rules.devices.iter() { + let device_instances = find_device_instances(&layout, &device.read().unwrap(), rules)?; + instances.insert(device.clone(), device_instances); + } + Ok(instances) +} +/// Find all instances of `device` in `layout` +fn find_device_instances( + layout: &FlatLayout, + device: &Device, + rules: &CircuitExtractionRules, +) -> Result, ()> { + let mut instances: Vec = Vec::new(); + // Many devices have several ports on the same layer, e.g. MOS source and drain. + // Get a unique hash-set of all relevant layers for intersection. + let mut port_layers = HashSet::new(); + for port in &device.ports { + port_layers.insert(port.layer.clone()); + } + // // Check each polygon in the layout on the device's ID layer + let id_layer = &device.id_layer; + for id_layer_elem_key in layout.by_layer[id_layer].iter() { + // Each ID-layer shape defines, or at least probably intends to define, an instance of this device. + // Check for intersecting shapes in each of its port-layers. + let id_layer_elem = &layout.elements[*id_layer_elem_key]; + if let Some(instance) = + is_this_an_instance(layout, device, &port_layers, id_layer_elem, rules)? + { + instances.push(instance); + } + } + Ok(instances) +} +/// Extract whether `id_layer_elem` creates an instance of `device` +fn is_this_an_instance( + layout: &FlatLayout, + device: &Device, + port_layers: &HashSet<(raw::LayerKey, raw::LayerPurpose)>, + id_layer_elem: &raw::Element, + rules: &CircuitExtractionRules, +) -> Result, ()> { + let mut intersecting_port_layer_elems: HashMap< + (raw::LayerKey, raw::LayerPurpose), + Vec, + > = HashMap::new(); + let mut nhits = 0; + for port_layer in port_layers { + for port_layer_elem_key in layout.by_layer[port_layer].iter() { + let port_layer_elem = &layout.elements[*port_layer_elem_key]; + if port_layer_elem.inner.intersects(&id_layer_elem.inner) { + intersecting_port_layer_elems + .entry(port_layer.clone()) + .or_insert_with(Vec::new) + .push(*port_layer_elem_key); + nhits += 1; + } + todo!(); + } + } + // Now sort out whether that set of intersections covers all of `device`s ports + // Easy case: if we found fewer hits than ports, we can't be an instance + if nhits < device.ports.len() { + return Ok(None); + } todo!() } - pub struct PrimaryLayer { pub name: String, pub desc: String, @@ -104,7 +213,7 @@ pub struct Device { pub desc: String, pub id: u64, /// Key-layer identifying the device - pub id_layer: Ptr, + pub id_layer: (raw::LayerKey, raw::LayerPurpose), // FIXME: Ptr, /// Port Layers pub ports: Vec, /// Additional, optional layers, which generally dictate device flavors or parameters @@ -137,7 +246,7 @@ pub enum BipolarKind { /// pub struct Port { pub name: String, - pub layer: Ptr, + pub layer: (raw::LayerKey, raw::LayerPurpose), // FIXME: Ptr, } pub struct Symmetries; From 97d060101f2a4088fe462da9fc404dbb382a9410 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 7 Apr 2022 13:33:24 -0700 Subject: [PATCH 13/28] Demo/ tutorial-style layout21raw inverter --- layout21raw/src/data.rs | 34 ++++++- layout21raw/src/geom.rs | 75 +++++++++++++++ layout21raw/src/proto.rs | 2 +- layout21raw/src/tests.rs | 166 +++++++++++++++++++++++++++++++-- layout21tetris/src/conv/raw.rs | 7 +- 5 files changed, 268 insertions(+), 16 deletions(-) diff --git a/layout21raw/src/data.rs b/layout21raw/src/data.rs index 3257a6e..e2f745b 100644 --- a/layout21raw/src/data.rs +++ b/layout21raw/src/data.rs @@ -17,7 +17,7 @@ use slotmap::{new_key_type, SlotMap}; use crate::{ bbox::{BoundBox, BoundBoxTrait}, error::{LayoutError, LayoutResult}, - geom::{Point, Shape, Transform, TransformTrait}, + geom::{Point, Shape, Transform, TransformTrait}, utils::{Ptr, PtrList}, }; @@ -165,11 +165,11 @@ impl Layers { } LayoutError::fail("No more layer numbers available") } - /// Get a reference to the [LayerKey] for layer-number `num` + /// Get the [LayerKey] for layer-number `num` pub fn keynum(&self, num: i16) -> Option { self.nums.get(&num).map(|x| x.clone()) } - /// Get a reference to the [LayerKey] layer-name `name` + /// Get the [LayerKey] for layer-name `name` pub fn keyname(&self, name: impl Into) -> Option { self.names.get(&name.into()).map(|x| x.clone()) } @@ -381,11 +381,13 @@ pub struct Library { } impl Library { /// Create a new and empty Library - pub fn new(name: impl Into, units: Units) -> Self { + pub fn new(name: impl Into, units: Units, layers: Option>) -> Self { + let layers = layers.unwrap_or_default(); Self { name: name.into(), units, - ..Default::default() + layers, + cells: PtrList::new(), } } } @@ -484,6 +486,13 @@ pub struct Layout { pub annotations: Vec, } impl Layout { + /// Create an empty [Layout] with the given `name` + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + ..Default::default() + } + } /// Create a rectangular [BoundBox] surrounding all elements in the [Layout]. pub fn bbox(&self) -> BoundBox { let mut bbox = BoundBox::empty(); @@ -564,6 +573,21 @@ pub struct Element { /// Shape pub inner: Shape, } +impl Element { + pub fn new( + net: Option>, + layer: LayerKey, + purpose: LayerPurpose, + inner: impl Into, + ) -> Self { + Self { + net: net.map(|s| s.into()), + layer, + purpose, + inner: inner.into(), + } + } +} /// Location, orientation, and angular rotation for an [Instance] /// Note these fields exist "flat" in [Instance] as well, diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs index 9738e7f..760d557 100644 --- a/layout21raw/src/geom.rs +++ b/layout21raw/src/geom.rs @@ -67,6 +67,11 @@ impl Point { } } } +impl From<(Int, Int)> for Point { + fn from(vals: (Int, Int)) -> Self { + Self::new(vals.0, vals.1) + } +} /// Direction Enumeration #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum Dir { @@ -100,6 +105,11 @@ pub struct Path { pub points: Vec, pub width: usize, } +impl Path { + pub fn new(points: Vec, width: usize) -> Self { + Self { points, width } + } +} /// # Polygon /// /// Closed n-sided polygon with arbitrary number of vertices. @@ -112,6 +122,50 @@ pub struct Path { pub struct Polygon { pub points: Vec, } +impl Polygon { + pub fn new(points: Vec) -> Self { + Self { points } + } +} +/// Polygon Builder +/// +/// Exposes a `move_by` interface for relative polygon construction. +/// The `build` method consumes each [PolygonBuilder] +/// and returns a [Polygon] in its place. +/// +/// FIXME: add a similar `PathBuilder`. +/// +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PolygonBuilder { + points: Vec, +} +impl PolygonBuilder { + /// Create a new [PolygonBuilder] from the origin. + pub fn new() -> Self { + Self { + points: vec![Point::new(0, 0)], + } + } + /// Create a new [PolygonBuilder] from a starting [Point]. + pub fn start_at(start: impl Into) -> Self { + Self { + points: vec![start.into()], + } + } + /// Move the "current point" by (x,y). + /// Adds a [Point] to the eventual [Polygon]. + pub fn move_by(&mut self, x: Int, y: Int) { + let last = self.points.last().unwrap(); + let new = last.shift(&Point::new(x, y)); + self.points.push(new); + } + /// Build to a [Polygon]. Consumes `self`. + pub fn build(self) -> Polygon { + Polygon { + points: self.points, + } + } +} /// # Rectangle /// /// Axis-aligned rectangle, specified by two opposite corners. @@ -122,6 +176,13 @@ pub struct Rect { pub p1: Point, } impl Rect { + /// Create a new [Rect] from two opposite corners + pub fn new(p0: impl Into, p1: impl Into) -> Self { + Self { + p0: p0.into(), + p1: p1.into(), + } + } /// Calculate our center-point pub fn center(&self) -> Point { Point::new((self.p0.x + self.p1.x) / 2, (self.p0.y + self.p1.y) / 2) @@ -166,6 +227,8 @@ pub trait ShapeTrait { fn contains(&self, pt: &Point) -> bool; /// Convert to a [Polygon], our most general of shapes fn to_poly(&self) -> Polygon; + /// Convert to a [Shape] enum, consuming `self` in the process + fn to_shape(self) -> Shape; } impl ShapeTrait for Rect { @@ -211,6 +274,10 @@ impl ShapeTrait for Rect { ], } } + /// Convert to a [Shape] enum, consuming `self` in the process + fn to_shape(self) -> Shape { + Shape::Rect(self) + } } impl ShapeTrait for Polygon { /// Retrieve our "origin", or first [Point] @@ -285,6 +352,10 @@ impl ShapeTrait for Polygon { fn to_poly(&self) -> Polygon { self.clone() } + /// Convert to a [Shape] enum, consuming `self` in the process + fn to_shape(self) -> Shape { + Shape::Polygon(self) + } } impl ShapeTrait for Path { /// Retrieve our "origin", or first [Point] @@ -338,6 +409,10 @@ impl ShapeTrait for Path { fn to_poly(&self) -> Polygon { unimplemented!("Path::to_poly") } + /// Convert to a [Shape] enum, consuming `self` in the process + fn to_shape(self) -> Shape { + Shape::Path(self) + } } /// # Matrix-Vector Transformation diff --git a/layout21raw/src/proto.rs b/layout21raw/src/proto.rs index d02cb09..ec2c585 100644 --- a/layout21raw/src/proto.rs +++ b/layout21raw/src/proto.rs @@ -625,7 +625,7 @@ impl ErrorHelper for ProtoImporter { #[test] fn proto1() -> LayoutResult<()> { // Round-trip through Layout21::Raw -> ProtoBuf -> Layout21::Raw - let mut lib = Library::new("prt_lib", Units::Nano); + let mut lib = Library::new("prt_lib", Units::Nano, None); let (layer, purpose) = { let mut layers = lib.layers.write()?; layers.get_or_insert(0, 0)? diff --git a/layout21raw/src/tests.rs b/layout21raw/src/tests.rs index 67c3fa8..f7882f0 100644 --- a/layout21raw/src/tests.rs +++ b/layout21raw/src/tests.rs @@ -3,6 +3,7 @@ //! use super::*; +use crate::utils::Ptr; #[test] fn point() { @@ -13,32 +14,36 @@ fn point() { /// Create a [Layers] used by a number of tests pub fn layers() -> LayoutResult { + use LayerPurpose::{Drawing, Label, Outline}; let mut layers = Layers::default(); // Add the outline/ boundary layer - layers.add(Layer::new(236, "boundary").add_pairs(&[(0, LayerPurpose::Outline)])?); + layers.add(Layer::new(236, "boundary").add_pairs(&[(0, Outline)])?); // Create metal layers - let metal_purps = [(20, LayerPurpose::Drawing), (5, LayerPurpose::Label)]; + let metal_purps = [(20, Drawing), (5, Label)]; layers.add(Layer::new(68, "met1").add_pairs(&metal_purps)?); layers.add(Layer::new(69, "met2").add_pairs(&metal_purps)?); layers.add(Layer::new(70, "met3").add_pairs(&metal_purps)?); layers.add(Layer::new(71, "met4").add_pairs(&metal_purps)?); + // Create the via layers // Note that while these use the same *GDS* layer number, they are separate [Layer] objects here. // (Because, well, they really are.) - let via_purps = [(44, LayerPurpose::Drawing)]; + let via_purps = [(44, Drawing)]; layers.add(Layer::new(67, "mcon").add_pairs(&via_purps)?); layers.add(Layer::new(68, "via").add_pairs(&via_purps)?); layers.add(Layer::new(69, "via2").add_pairs(&via_purps)?); layers.add(Layer::new(70, "via3").add_pairs(&via_purps)?); layers.add(Layer::new(71, "via4").add_pairs(&via_purps)?); - // Add a base-layer - layers.add( - Layer::new(64, "nwell") - .add_pairs(&[(44, LayerPurpose::Drawing), (5, LayerPurpose::Label)])?, - ); + // Add base-layers + // FIXME: check these datatype numbers! + layers.add(Layer::new(64, "nwell").add_pairs(&[(20, Drawing), (5, Label)])?); + layers.add(Layer::new(65, "diff").add_pairs(&[(20, Drawing), (5, Label)])?); + layers.add(Layer::new(66, "poly").add_pairs(&[(20, Drawing), (5, Label)])?); + layers.add(Layer::new(67, "li1").add_pairs(&[(20, Drawing), (5, Label)])?); + Ok(layers) } #[test] @@ -60,6 +65,151 @@ fn test_layers() -> LayoutResult<()> { Ok(()) } +#[test] +fn polygon_builder() -> LayoutResult<()> { + let mut b = PolygonBuilder::start_at((0, 0)); + + // Create an "H" shape + b.move_by(1, 0); + b.move_by(0, 1); + b.move_by(1, 0); + b.move_by(0, -1); + b.move_by(1, 0); + b.move_by(0, 3); + b.move_by(-1, 0); + b.move_by(0, -1); + b.move_by(-1, 0); + b.move_by(0, 1); + b.move_by(-1, 0); + + // Convert to a polygon + let p = b.build(); + assert_eq!( + p.points, + vec![ + Point { x: 0, y: 0 }, + Point { x: 1, y: 0 }, + Point { x: 1, y: 1 }, + Point { x: 2, y: 1 }, + Point { x: 2, y: 0 }, + Point { x: 3, y: 0 }, + Point { x: 3, y: 3 }, + Point { x: 2, y: 3 }, + Point { x: 2, y: 2 }, + Point { x: 1, y: 2 }, + Point { x: 1, y: 3 }, + Point { x: 0, y: 3 }, + ] + ); + + Ok(()) +} + +/// Test [Layout] creation and adding a few elements +#[test] +fn create_layout() -> LayoutResult<()> { + use LayerPurpose::Drawing; + let layers = layers()?; + let met1 = layers.keyname("met1").unwrap(); + let mut lay = Layout::new("test_layout_creation"); + lay.elems.push(Element::new( + Some("vss"), + met1, + Drawing, + Rect::new(Point::new(0, 0), Point::new(10, 10)), + )); + lay.elems.push(Element { + net: Some("vdd".into()), + layer: met1, + purpose: Drawing, + inner: Shape::Rect(Rect::new(Point::new(0, 10), Point::new(10, 10))), + }); + lay.elems.push(Element { + net: Some("something_else".into()), + layer: met1, + purpose: Drawing, + inner: Shape::Polygon(Polygon::new(vec![ + Point::new(0, 10), + Point::new(10, 10), + Point::new(10, 0), + Point::new(0, 0), + ])), + }); + + // FIXME: more specific content checks + + Ok(()) +} + +/// Make a tutorial-grade inverter! +#[test] +#[cfg(all(feature = "gds", feature = "proto"))] +fn create_inverter() -> LayoutResult<()> { + use LayerPurpose::Drawing; + let layers = layers()?; + + let met1 = layers.keyname("met1").unwrap(); + let diff = layers.keyname("diff").unwrap(); + let poly = layers.keyname("poly").unwrap(); + let nwell = layers.keyname("nwell").unwrap(); + let li1 = layers.keyname("li1").unwrap(); + + let mut layout = Layout::new("inv"); + + layout.elems.push(Element { + net: None, + layer: diff, + purpose: Drawing, + inner: Rect::new(Point::new(0, 400), Point::new(500, 500)).into(), + }); + layout.elems.push(Element { + net: None, + layer: diff, + purpose: Drawing, + inner: Rect::new(Point::new(0, 0), Point::new(500, 100)).into(), + }); + layout.elems.push(Element { + net: Some("VSS".into()), + layer: met1, + purpose: Drawing, + inner: Rect::new(Point::new(-100, -100), Point::new(600, 100)).into(), + }); + layout.elems.push(Element { + net: Some("VDD".into()), + layer: met1, + purpose: Drawing, + inner: Rect::new(Point::new(-100, 400), Point::new(600, 600)).into(), + }); + layout.elems.push(Element { + net: Some("inp".into()), + layer: poly, + purpose: Drawing, + inner: Rect::new(Point::new(300, 0), Point::new(400, 600)).into(), + }); + layout.elems.push(Element { + net: Some("VDD".into()), + layer: nwell, + purpose: Drawing, + inner: Rect::new(Point::new(-100, 300), Point::new(600, 600)).into(), + }); + layout.elems.push(Element { + net: Some("VDD".into()), + layer: li1, + purpose: Drawing, + inner: Path::new(vec![Point::new(-100, 300), Point::new(600, 600)], 50).into(), + }); + + // FIXME: add the rest of the content! + + let mut lib = Library::new("invlib", Units::Nano, Some(Ptr::new(layers))); + lib.cells.insert(layout); + + let gds = lib.to_gds()?; + gds.save("invlib.gds")?; + let proto = lib.to_proto()?; + // FIXME: write that to disk too + Ok(()) +} /// Take a trip through GDSII -> Layout21::Raw -> ProtoBuf #[cfg(all(feature = "gds", feature = "proto"))] diff --git a/layout21tetris/src/conv/raw.rs b/layout21tetris/src/conv/raw.rs index 24daac2..bfeb49b 100644 --- a/layout21tetris/src/conv/raw.rs +++ b/layout21tetris/src/conv/raw.rs @@ -131,8 +131,11 @@ impl<'lib> RawExporter { // Get our starter raw-lib, either anew or from any we've imported let rawlibptr = if self.lib.rawlibs.len() == 0 { // Create a new [raw::Library] - let mut rawlib = raw::Library::new(&self.lib.name, self.stack.units); - rawlib.layers = Ptr::clone(self.stack.rawlayers.as_ref().unwrap()); + let mut rawlib = raw::Library::new( + &self.lib.name, + self.stack.units, + Some(Ptr::clone(self.stack.rawlayers.as_ref().unwrap())), + ); Ok(Ptr::new(rawlib)) } else if self.lib.rawlibs.len() == 1 { // Pop the sole raw-library, and use it as a starting point From ddc034a05cda43f2d91bb42ea4d77b8ad40bccfc Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 10 May 2022 23:39:26 -0700 Subject: [PATCH 14/28] Create Tetris python package --- Tetris/.gitignore | 152 +++++ Tetris/LICENSE | 29 + Tetris/readme.md | 5 + Tetris/setup.py | 34 ++ Tetris/tests/__init__.py | 0 Tetris/tests/test_tetris.py | 11 + Tetris/tetris/__init__.py | 5 + Tetris/tetris/abs.rs | 116 ++++ Tetris/tetris/array.rs | 94 +++ Tetris/tetris/bbox.rs | 58 ++ Tetris/tetris/cell.rs | 194 +++++++ Tetris/tetris/conv/mod.rs | 6 + Tetris/tetris/conv/proto.rs | 605 +++++++++++++++++++ Tetris/tetris/conv/raw.rs | 851 +++++++++++++++++++++++++++ Tetris/tetris/coords.rs | 359 ++++++++++++ Tetris/tetris/group.rs | 51 ++ Tetris/tetris/instance.rs | 77 +++ Tetris/tetris/interface.rs | 36 ++ Tetris/tetris/layout.rs | 104 ++++ Tetris/tetris/lib.rs | 35 ++ Tetris/tetris/library.rs | 92 +++ Tetris/tetris/outline.rs | 99 ++++ Tetris/tetris/placement.rs | 212 +++++++ Tetris/tetris/placer.rs | 1000 ++++++++++++++++++++++++++++++++ Tetris/tetris/stack.rs | 348 +++++++++++ Tetris/tetris/tests/demos.rs | 21 + Tetris/tetris/tests/empty.yaml | 13 + Tetris/tetris/tests/insts.yaml | 32 + Tetris/tetris/tests/mod.rs | 229 ++++++++ Tetris/tetris/tests/ro.rs | 474 +++++++++++++++ Tetris/tetris/tests/stacks.rs | 187 ++++++ Tetris/tetris/tracks.rs | 407 +++++++++++++ Tetris/tetris/validate.rs | 375 ++++++++++++ 33 files changed, 6311 insertions(+) create mode 100644 Tetris/.gitignore create mode 100644 Tetris/LICENSE create mode 100644 Tetris/readme.md create mode 100644 Tetris/setup.py create mode 100644 Tetris/tests/__init__.py create mode 100644 Tetris/tests/test_tetris.py create mode 100644 Tetris/tetris/__init__.py create mode 100644 Tetris/tetris/abs.rs create mode 100644 Tetris/tetris/array.rs create mode 100644 Tetris/tetris/bbox.rs create mode 100644 Tetris/tetris/cell.rs create mode 100644 Tetris/tetris/conv/mod.rs create mode 100644 Tetris/tetris/conv/proto.rs create mode 100644 Tetris/tetris/conv/raw.rs create mode 100644 Tetris/tetris/coords.rs create mode 100644 Tetris/tetris/group.rs create mode 100644 Tetris/tetris/instance.rs create mode 100644 Tetris/tetris/interface.rs create mode 100644 Tetris/tetris/layout.rs create mode 100644 Tetris/tetris/lib.rs create mode 100644 Tetris/tetris/library.rs create mode 100644 Tetris/tetris/outline.rs create mode 100644 Tetris/tetris/placement.rs create mode 100644 Tetris/tetris/placer.rs create mode 100644 Tetris/tetris/stack.rs create mode 100644 Tetris/tetris/tests/demos.rs create mode 100644 Tetris/tetris/tests/empty.yaml create mode 100644 Tetris/tetris/tests/insts.yaml create mode 100644 Tetris/tetris/tests/mod.rs create mode 100644 Tetris/tetris/tests/ro.rs create mode 100644 Tetris/tetris/tests/stacks.rs create mode 100644 Tetris/tetris/tracks.rs create mode 100644 Tetris/tetris/validate.rs diff --git a/Tetris/.gitignore b/Tetris/.gitignore new file mode 100644 index 0000000..b176143 --- /dev/null +++ b/Tetris/.gitignore @@ -0,0 +1,152 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/Tetris/LICENSE b/Tetris/LICENSE new file mode 100644 index 0000000..2a2ea6c --- /dev/null +++ b/Tetris/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, Dan Fritchman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Tetris/readme.md b/Tetris/readme.md new file mode 100644 index 0000000..6bac663 --- /dev/null +++ b/Tetris/readme.md @@ -0,0 +1,5 @@ + +# Tetris + +Gridded Semi-Custom Integrated Circuit Layout + diff --git a/Tetris/setup.py b/Tetris/setup.py new file mode 100644 index 0000000..4d4555c --- /dev/null +++ b/Tetris/setup.py @@ -0,0 +1,34 @@ +""" +# Setup Script + +Derived from the setuptools sample project at +https://github.com/pypa/sampleproject/blob/main/setup.py + +""" + +# Always prefer setuptools over distutils +from setuptools import setup, find_packages +import pathlib + +here = pathlib.Path(__file__).parent.resolve() + +# Get the long description from the README file +long_description = (here / "readme.md").read_text(encoding="utf-8") + +_VLSIR_VERSION = "1.0.0.dev0" + +setup( + name="tetris", + version=_VLSIR_VERSION, + description="Gridded Semi-Custom Integrated Circuit Layout", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/dan-fritchman/Layout21", + author="Dan Fritchman", + packages=find_packages(), + python_requires=">=3.7, <4", + install_requires=[f"vlsir=={_VLSIR_VERSION}", "numpy==1.21.5"], + extras_require={ + "dev": ["pytest==5.2", "coverage", "pytest-cov", "black==19.10b0", "twine"] + }, +) diff --git a/Tetris/tests/__init__.py b/Tetris/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Tetris/tests/test_tetris.py b/Tetris/tests/test_tetris.py new file mode 100644 index 0000000..471d1dd --- /dev/null +++ b/Tetris/tests/test_tetris.py @@ -0,0 +1,11 @@ +""" +# Unit Tests +""" + +import pytest +import tetris + + +def test_version(): + assert tetris.__version__ == "1.0.0.dev0" + diff --git a/Tetris/tetris/__init__.py b/Tetris/tetris/__init__.py new file mode 100644 index 0000000..d71ca68 --- /dev/null +++ b/Tetris/tetris/__init__.py @@ -0,0 +1,5 @@ + +__version__ = "1.0.0.dev0" + + + diff --git a/Tetris/tetris/abs.rs b/Tetris/tetris/abs.rs new file mode 100644 index 0000000..3802ef5 --- /dev/null +++ b/Tetris/tetris/abs.rs @@ -0,0 +1,116 @@ +//! +//! # Abstract Layout Module +//! +//! [Abstract] layouts describe a [Cell]'s outline and physical interface, without exposing implementation details. +//! [Cell]-[Abstract]s primarily comprise their outlines and pins. +//! Outlines follow the same "Tetris-Shapes" as `layout21::tetris` layout cells, including the requirements for a uniform z-axis. +//! Internal layers are "fully blocked", in that parent layouts may not route through them. +//! In legacy layout systems this would be akin to including blockages of the same shape as [Outline] on each layer. +//! +//! Sadly the english-spelled name "abstract" is reserved as a potential +//! [future Rust keyword](https://doc.rust-lang.org/reference/keywords.html#reserved-keywords), +//! and is hence avoided as an identifier throughout Layout21. +//! + +// Crates.io +use serde::{Deserialize, Serialize}; + +// Local imports +use crate::outline; +use crate::stack::RelZ; + +/// Abstract-Layout +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Abstract { + /// Cell Name + pub name: String, + /// Outline in "Tetris-Shapes" + pub outline: outline::Outline, + /// Number of Metal Layers Used + pub metals: usize, + /// Ports + pub ports: Vec, +} +impl Abstract { + /// Create a new abstract layout. No ports are initially defined. + pub fn new(name: impl Into, metals: usize, outline: outline::Outline) -> Self { + Self { + name: name.into(), + outline, + metals, + ports: Vec::new(), + } + } + /// Retrieve a reference to a port by name. + /// Returns `None` if no port with that name exists. + pub fn port(&self, name: &str) -> Option<&Port> { + for port in &self.ports { + if port.name == name { + return Some(port); + } + } + None + } +} +/// Abstract-Layout Port +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Port { + /// Port/ Signal Name + pub name: String, + /// Physical Info + pub kind: PortKind, +} +/// Abstract-Layout Port Inner Detail +/// +/// All location and "geometric" information per Port is stored here, +/// among a few enumerated variants. +/// +/// Ports may either connect on x/y edges, or on the top (in the z-axis) layer. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PortKind { + /// Ports which connect on x/y outline edges + Edge { + layer: usize, + track: usize, + side: Side, + }, + /// Ports accessible from bot top *and* top-layer edges + /// Note their `layer` field is implicitly defined as the cell's `metals`. + ZTopEdge { + /// Track Index + track: usize, + /// Side + side: Side, + /// Location into which the pin extends inward + into: (usize, RelZ), + }, + /// Ports which are internal to the cell outline, + /// but connect from above in the z-stack. + /// These can be assigned at several locations across their track, + /// and are presumed to be internally-connected between such locations. + ZTopInner { + /// Locations + locs: Vec, + }, +} +/// A location (track intersection) on our top z-axis layer +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TopLoc { + /// Track Index + track: usize, + /// Intersecting Track Index + at: usize, + /// Whether `at` refers to the track-indices above or below + relz: RelZ, +} +/// # Port Side Enumeration +/// +/// Note there are only two such sides: the "origin-side" [BottomOrLeft] and the "width-side" [TopOrRight]. +/// Each [Layer]'s orientation ([Dir]) dictates between bottom/left and top/right. +/// Also note the requirements on [Outline] shapes ensure each track has a unique left/right or top/bottom pair of edges. +/// +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum Side { + BottomOrLeft, + TopOrRight, +} diff --git a/Tetris/tetris/array.rs b/Tetris/tetris/array.rs new file mode 100644 index 0000000..d073980 --- /dev/null +++ b/Tetris/tetris/array.rs @@ -0,0 +1,94 @@ +//! +//! # Layout Arrays +//! +//! Uniformly-spaced repetitions of [Arrayable] elements. +//! + +// Local imports +use crate::{ + bbox::{BoundBox, HasBoundBox}, + cell::Cell, + coords::{PrimPitches, Xy}, + group::Group, + placement::{Place, Separation}, + raw::{LayoutError, LayoutResult}, + utils::Ptr, +}; + +/// Uniform-Spaced Array of Identical [Placeable] Elements +#[derive(Debug, Clone)] +pub struct Array { + /// Array Name + pub name: String, + /// Unit to be Arrayed + pub unit: Arrayable, + /// Number of elements + pub count: usize, + /// Separation between elements + /// FIXME: whether to include the size of the element or not + pub sep: Separation, +} +impl Array { + /// Size of the Array's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. + pub fn boundbox_size(&self) -> LayoutResult> { + let _unit = self.unit.boundbox_size()?; + todo!() // FIXME: do some math on separation, size + } +} +/// Enumeration of types that can be Arrayed +#[derive(Debug, Clone)] +pub enum Arrayable { + /// Instance of a Cell + Instance(Ptr), + /// Uniform array of placeable elements + Array(Ptr), + /// Group of other placeable elements + Group(Ptr), +} +impl Arrayable { + pub fn boundbox_size(&self) -> LayoutResult> { + match self { + Arrayable::Instance(ref p) => p.read()?.boundbox_size(), + Arrayable::Array(ref p) => p.read()?.boundbox_size(), + Arrayable::Group(ref p) => p.read()?.boundbox_size(), + } + } +} + +/// Located Instance of an Array +#[derive(Debug, Clone)] +pub struct ArrayInstance { + /// Array-Instance Name + pub name: String, + /// Array Definition + pub array: Ptr, + /// Location of first element + pub loc: Place>, + /// Vertical reflection + pub reflect_vert: bool, + /// Horizontal reflection + pub reflect_horiz: bool, +} + +impl HasBoundBox for ArrayInstance { + type Units = PrimPitches; + type Error = LayoutError; + /// Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. + /// Instance location must be resolved to absolute coordinates, or this method will fail. + fn boundbox(&self) -> LayoutResult> { + // FIXME: share most or all of this with [Instance] + + let loc = self.loc.abs()?; + let array = self.array.read()?; + let outline = array.boundbox_size()?; + let (x0, x1) = match self.reflect_horiz { + false => (loc.x, loc.x + outline.x), + true => (loc.x - outline.x, loc.x), + }; + let (y0, y1) = match self.reflect_vert { + false => (loc.y, loc.y + outline.y), + true => (loc.y - outline.y, loc.y), + }; + Ok(BoundBox::new(Xy::new(x0, y0), Xy::new(x1, y1))) + } +} diff --git a/Tetris/tetris/bbox.rs b/Tetris/tetris/bbox.rs new file mode 100644 index 0000000..6150c50 --- /dev/null +++ b/Tetris/tetris/bbox.rs @@ -0,0 +1,58 @@ +//! +//! # Rectangular Bounding Boxes +//! + +// Crates.io +use serde::{Deserialize, Serialize}; + +// Local imports +use crate::coords::{HasUnits, Xy}; +use crate::placement::Side; + +/// # Bounding Rectangular Box +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct BoundBox { + pub p0: Xy, + pub p1: Xy, +} +impl BoundBox { + /// Create a new [BoundBox] from two [Xy] points. + pub fn new(p0: Xy, p1: Xy) -> Self { + Self { p0, p1 } + } + /// Retrieve our coordinate at [Side] `side`. + pub fn side(&self, side: Side) -> T { + match side { + Side::Left => self.p0.x, + Side::Right => self.p1.x, + Side::Bottom => self.p0.y, + Side::Top => self.p1.y, + } + } +} +impl BoundBox { + /// Create a new [BoundBox] from potentially unordered pairs of x and y coordinates. + pub fn from_xy(xs: (T, T), ys: (T, T)) -> Self { + let (x0, x1) = if xs.0 < xs.1 { + (xs.0, xs.1) + } else { + (xs.1, xs.0) + }; + let (y0, y1) = if ys.0 < ys.1 { + (ys.0, ys.1) + } else { + (ys.1, ys.0) + }; + Self::new(Xy::new(x0, y0), Xy::new(x1, y1)) + } +} + +/// Trait for types that can be converted to a [BoundBox]. +/// +/// Includes a single method `boundbox` which returns a [BoundBox] of the type. +pub trait HasBoundBox { + type Units: HasUnits; + type Error; + /// Get a [BoundBox] of the type. + fn boundbox(&self) -> Result, Self::Error>; +} diff --git a/Tetris/tetris/cell.rs b/Tetris/tetris/cell.rs new file mode 100644 index 0000000..d798581 --- /dev/null +++ b/Tetris/tetris/cell.rs @@ -0,0 +1,194 @@ +//! +//! # Cell Definition +//! +//! Defines the [Cell] type, which represents a multi-viewed piece of reusable hardware. +//! [Cell]s can, and generally do, have one or more associated "views", +//! including [Abstract]s, [Layout], interface definitions, and/or "raw" layouts. +//! + +// Crates.io +use derive_more; + +// Local imports +use crate::coords::{PrimPitches, Xy}; +use crate::layout::Layout; +use crate::raw::{LayoutError, LayoutResult}; +use crate::utils::Ptr; +use crate::{abs, interface, outline, raw}; + +/// "Pointer" to a raw (lib, cell) combination. +/// Wraps with basic [Outline] and `metals` information to enable bounded placement. +#[derive(Debug, Clone)] +pub struct RawLayoutPtr { + /// Outline shape, counted in x and y pitches of `stack` + pub outline: outline::Outline, + /// Number of Metal Layers Used + pub metals: usize, + /// Pointer to the raw Library + pub lib: Ptr, + /// Pointer to the raw Cell + pub cell: Ptr, +} +/// # Cell View Enumeration +/// All of the ways in which a Cell is represented +#[derive(derive_more::From, Debug, Clone)] +pub enum CellView { + Interface(interface::Bundle), + Abstract(abs::Abstract), + Layout(Layout), + RawLayoutPtr(RawLayoutPtr), +} + +/// Collection of the Views describing a Cell +#[derive(Debug, Default, Clone)] +pub struct Cell { + /// Cell Name + pub name: String, + /// Interface + pub interface: Option, + /// Layout Abstract + pub abs: Option, + /// Layout Implementation + pub layout: Option, + /// Raw Layout + /// FIXME: this should probably move "up" a level, + /// so that cells are either defined as `raw` or `tetris` implementations, + /// but not both + pub raw: Option, +} +impl Cell { + /// Create a new and initially empty [Cell] + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + ..Default::default() + } + } + /// Add [CellView] `view` to our appropriate type-based field. + pub fn add_view(&mut self, view: impl Into) { + let view = view.into(); + match view { + CellView::Interface(x) => { + self.interface.replace(x); + } + CellView::Abstract(x) => { + self.abs.replace(x); + } + CellView::Layout(x) => { + self.layout.replace(x); + } + CellView::RawLayoutPtr(x) => { + self.raw.replace(x); + } + } + } + /// Create from a list of [CellView]s and a name. + pub fn from_views(name: impl Into, views: Vec) -> Self { + let mut myself = Self::default(); + myself.name = name.into(); + for view in views { + myself.add_view(view); + } + myself + } + /// Return whichever view highest-prioritorily dictates the outline + pub fn outline(&self) -> LayoutResult<&outline::Outline> { + // We take the "most abstract" view for the outline + // (although if there are more than one, they better be the same... + // FIXME: this should be a validation step.) + // Overall this method probably should move to a "validated" cell in which each view is assured consistent. + if let Some(ref x) = self.abs { + Ok(&x.outline) + } else if let Some(ref x) = self.layout { + Ok(&x.outline) + } else if let Some(ref x) = self.raw { + Ok(&x.outline) + } else { + LayoutError::fail(format!( + "Failed to retrieve outline of cell {} with no abstract or implementation", + self.name, + )) + } + } + /// Size of the [Cell]'s rectangular `boundbox`. + pub fn boundbox_size(&self) -> LayoutResult> { + let outline = self.outline()?; + Ok(Xy::new(outline.xmax(), outline.ymax())) + } + /// Return whichever view highest-prioritorily dictates the top-layer + pub fn metals(&self) -> LayoutResult { + // FIXME: same commentary as `outline` above + if let Some(ref x) = self.abs { + Ok(x.metals) + } else if let Some(ref x) = self.layout { + Ok(x.metals) + } else if let Some(ref x) = self.raw { + Ok(x.metals) + } else { + LayoutError::fail(format!( + "Failed to retrieve metal-layers of cell {} with no abstract or implementation", + self.name, + )) + } + } + /// Get the cell's top metal layer (numer). + /// Returns `None` if no metal layers are used. + pub fn top_metal(&self) -> LayoutResult> { + let metals = self.metals()?; + if metals == 0 { + Ok(None) + } else { + Ok(Some(metals - 1)) + } + } +} +impl From for Cell { + fn from(src: CellView) -> Self { + match src { + CellView::Interface(x) => x.into(), + CellView::Abstract(x) => x.into(), + CellView::Layout(x) => x.into(), + CellView::RawLayoutPtr(x) => x.into(), + } + } +} +impl From for Cell { + fn from(src: interface::Bundle) -> Self { + Self { + name: src.name.clone(), + interface: Some(src), + ..Default::default() + } + } +} +impl From for Cell { + fn from(src: abs::Abstract) -> Self { + Self { + name: src.name.clone(), + abs: Some(src), + ..Default::default() + } + } +} +impl From for Cell { + fn from(src: Layout) -> Self { + Self { + name: src.name.clone(), + layout: Some(src), + ..Default::default() + } + } +} +impl From for Cell { + fn from(src: RawLayoutPtr) -> Self { + let name = { + let cell = src.cell.read().unwrap(); + cell.name.clone() + }; + Self { + name, + raw: Some(src), + ..Default::default() + } + } +} diff --git a/Tetris/tetris/conv/mod.rs b/Tetris/tetris/conv/mod.rs new file mode 100644 index 0000000..23ba02c --- /dev/null +++ b/Tetris/tetris/conv/mod.rs @@ -0,0 +1,6 @@ +//! +//! Conversion Modules +//! + +pub mod proto; +pub mod raw; diff --git a/Tetris/tetris/conv/proto.rs b/Tetris/tetris/conv/proto.rs new file mode 100644 index 0000000..23c03b2 --- /dev/null +++ b/Tetris/tetris/conv/proto.rs @@ -0,0 +1,605 @@ +//! +//! # ProtoBuf Import & Export +//! + +// Std-Lib +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; + +// Local imports +use crate::{ + // FIXME: some of these should come from `validate` + abs::{Abstract, Port}, + cell::Cell, + coords::{HasUnits, PrimPitches, Xy}, + instance::Instance, + layout::Layout, + library::Library, + outline::Outline, + placement::Place, + raw::{Dir, LayoutError, LayoutResult}, + stack::{Assign, RelZ}, + tracks::{TrackCross, TrackRef}, + utils::{DepOrder, DepOrderer, ErrorContext, ErrorHelper, Ptr}, +}; +// Proto-crate imports and aliases +use layout21protos as proto; +use proto::raw as rawproto; +use proto::tetris as tproto; + +/// # ProtoBuf Exporter +#[derive(Debug)] +pub struct ProtoExporter<'lib> { + lib: &'lib Library, // Source [Library] + ctx: Vec, // Error Stack +} +impl<'lib> ProtoExporter<'lib> { + pub fn export(lib: &'lib Library) -> LayoutResult { + Self { + lib, + ctx: Vec::new(), + } + .export_lib() + } + /// Internal implementation method. Convert all, starting from our top-level [Library]. + fn export_lib(&mut self) -> LayoutResult { + self.ctx.push(ErrorContext::Library(self.lib.name.clone())); + // Create a new [tproto::Library] + let mut plib = tproto::Library::default(); + + // Set its library name + plib.domain = self.lib.name.clone(); + // And convert each of our cells + for cell in CellOrder::order(&self.lib.cells)?.iter() { + let pcell = self.export_cell(&*cell.read()?)?; + plib.cells.push(pcell); + } + self.ctx.pop(); + Ok(plib) + } + /// Convert a [Cell] to a [tproto::Cell] cell-definition + fn export_cell(&mut self, cell: &Cell) -> LayoutResult { + self.ctx.push(ErrorContext::Cell(cell.name.clone())); + let mut pcell = tproto::Cell::default(); + pcell.name = cell.name.clone(); + + if let Some(ref lay) = cell.layout { + pcell.layout = Some(self.export_layout(lay)?); + } + if let Some(ref a) = cell.abs { + pcell.r#abstract = Some(self.export_abstract(a)?); + } + self.ctx.pop(); + Ok(pcell) + } + /// Convert a [Abstract] + fn export_abstract(&mut self, abs: &Abstract) -> LayoutResult { + self.ctx.push(ErrorContext::Abstract); + // Create the new [tproto::Abstract] + let mut pabs = tproto::Abstract::default(); + // Convert our name + pabs.name = abs.name.clone(); + // Convert its ports + for port in abs.ports.iter() { + let pport = self.export_abstract_port(&port, abs.metals)?; + pabs.ports.push(pport); + } + // Convert its outline + pabs.outline = Some(self.export_outline(&abs.outline, abs.metals)?); + // And we're done - pop the context and return + self.ctx.pop(); + Ok(pabs) + } + /// Export an [Outline] + fn export_outline( + &mut self, + outline: &Outline, + metals: usize, + ) -> LayoutResult { + let x = self.export_dimensions(outline.x.as_slice())?; + let y = self.export_dimensions(outline.y.as_slice())?; + let metals = i64::try_from(metals)?; + Ok(tproto::Outline { x, y, metals }) + } + /// Export an abstract's [Port] + fn export_abstract_port( + &mut self, + port: &Port, + metals: usize, + ) -> LayoutResult { + use crate::abs::{PortKind, Side}; + let mut pport = tproto::AbstractPort::default(); + pport.net = port.name.clone(); + + use tproto::abstract_port::{EdgePort, Kind, PortSide, ZTopEdgePort, ZTopInner}; + let kind = match &port.kind { + PortKind::Edge { layer, track, side } => { + let track = Some(tproto::TrackRef { + layer: i64::try_from(*layer)?, + track: i64::try_from(*track)?, + }); + let side = match side { + Side::BottomOrLeft => PortSide::BottomOrLeft, + Side::TopOrRight => PortSide::TopOrRight, + }; + let side = i32::from(side); + Kind::Edge(EdgePort { track, side }) + } + PortKind::ZTopEdge { track, side, into } => { + let track = i64::try_from(*track)?; + let into = { + let layer = match into.1 { + RelZ::Above => metals + 1, + RelZ::Below => metals - 1, + }; + Some(tproto::TrackRef { + layer: i64::try_from(layer)?, + track: i64::try_from(into.0)?, + }) + }; + let side = match side { + Side::BottomOrLeft => PortSide::BottomOrLeft, + Side::TopOrRight => PortSide::TopOrRight, + }; + let side = i32::from(side); + + Kind::ZtopEdge(ZTopEdgePort { track, side, into }) + } + PortKind::ZTopInner { locs: _ } => todo!(), + }; + pport.kind = Some(kind); + Ok(pport) + } + /// Export a [Layout] to a [tproto::Layout] cell-implementation + fn export_layout(&mut self, layout: &Layout) -> LayoutResult { + self.ctx.push(ErrorContext::Impl); + // Create the empty/default [tproto::Layout] + let mut playout = tproto::Layout::default(); + // Convert our name + playout.name = layout.name.clone(); + playout.outline = Some(self.export_outline(&layout.outline, layout.metals)?); + // Convert each [Instance] + for ptr in layout.instances.iter() { + let inst = ptr.read()?; + playout.instances.push(self.export_instance(&*inst)?); + } + // Convert each [Assign] + for assn in &layout.assignments { + playout.assignments.push(self.export_assignment(assn)?); + } + // Convert each [Cut] + for cut in &layout.cuts { + playout.cuts.push(self.export_track_cross(cut)?); + } + self.ctx.pop(); + Ok(playout) + } + /// Convert an [Instance] to a [tproto::Instance] + fn export_instance(&mut self, inst: &Instance) -> LayoutResult { + let cell = inst.cell.read()?; + let abs_loc = inst.loc.abs()?; // FIXME: asserts the instance has an absolute location, for now + let loc = self.export_point(&abs_loc)?; + + // This is where the auto-generated proto-`oneof`s get fun. + // proto::tetris::Place <= this is a struct + // proto::tetris::place <= this is a `mod`, lower-case named after that struct + // proto::tetris::place::Place <= this is an enum, upper-case named after that `mod` + // Got it? + let loc = Some(proto::tetris::Place { + place: Some(proto::tetris::place::Place::Abs(loc)), + }); + + Ok(tproto::Instance { + name: inst.inst_name.clone(), + cell: Some(proto::utils::Reference { + to: Some(proto::utils::reference::To::Local(cell.name.clone())), + }), + reflect_vert: inst.reflect_vert, + reflect_horiz: inst.reflect_horiz, + loc, + }) + } + /// Export an [Assign] to a [tproto::Assign] + fn export_assignment(&mut self, assn: &Assign) -> LayoutResult { + let mut passn = tproto::Assign::default(); + passn.net = assn.net.clone(); + passn.at = Some(self.export_track_cross(&assn.at)?); + Ok(passn) + } + /// Export a [TrackCross] + fn export_track_cross(&mut self, cross: &TrackCross) -> LayoutResult { + let track = Some(self.export_track_ref(&cross.track)?); + let cross = Some(self.export_track_ref(&cross.cross)?); + let pcross = tproto::TrackCross { track, cross }; + Ok(pcross) + } + /// Export a [TrackRef] + fn export_track_ref(&mut self, track: &TrackRef) -> LayoutResult { + let layer = i64::try_from(track.layer)?; + let track = i64::try_from(track.track)?; + Ok(tproto::TrackRef { layer, track }) + } + /// Export a list of [HasUnit] dimensioned distance-values to + fn export_dimensions(&mut self, p: &[T]) -> LayoutResult> { + let mut rv = Vec::with_capacity(p.len()); + for val in p { + rv.push(self.export_dimension(val)?); + } + Ok(rv) + } + /// Export a [HasUnit] dimensioned distance-value to `i64` + fn export_dimension(&mut self, p: &T) -> LayoutResult { + // Convert to no-unit integers, and then to i64 + Ok(i64::try_from(p.raw())?) + } + /// Export a [Point] + fn export_point(&mut self, p: &Xy) -> LayoutResult { + let p = p.raw(); // Convert to no-unit integers + let x = i64::try_from(p.x)?; // Convert into i64 + let y = i64::try_from(p.y)?; + Ok(rawproto::Point::new(x, y)) // And send back a proto-Point + } +} // impl ProtoExporter +impl ErrorHelper for ProtoExporter<'_> { + type Error = LayoutError; + fn err(&self, msg: impl Into) -> LayoutError { + LayoutError::Export { + message: msg.into(), + stack: self.ctx.clone(), + } + } +} + +/// Empty struct for implementing the [DepOrder] trait for library [Cell]s +struct CellOrder; +impl DepOrder for CellOrder { + type Item = Ptr; + type Error = LayoutError; + + /// Process [Cell]-pointer `item` + /// Follow its `instances` list, pushing their `cell` to the stack + fn process(item: &Ptr, orderer: &mut DepOrderer) -> LayoutResult<()> { + let cell = item.read()?; + if let Some(layout) = &cell.layout { + // Push all instances first + for ptr in layout.instances.iter() { + let inst = ptr.read()?; + orderer.push(&inst.cell)?; + } + } + Ok(()) + } + fn fail() -> Result<(), Self::Error> { + LayoutError::fail("Cell ordering error") + } +} + +/// # ProtoBuf Library Importer +#[derive(Debug, Default)] +pub struct ProtoLibImporter { + ctx: Vec, // Error Stack + cell_map: HashMap>, // Proto cell-name => [Cell] +} +impl ProtoLibImporter { + pub fn import(plib: &tproto::Library) -> LayoutResult { + // Run the main import-implementation method + Self::default().import_lib(&plib) + } + /// Internal implementation method. Convert the top-level library. + fn import_lib(&mut self, plib: &tproto::Library) -> LayoutResult { + let name = plib.domain.clone(); + self.ctx.push(ErrorContext::Library(name.clone())); + // Give our library its name + let mut lib = Library::new(name); + // And convert each of its `cells` + for cell in &plib.cells { + let name = cell.name.clone(); + let cell = self.import_cell(cell)?; + let cellkey = lib.cells.insert(cell); + self.cell_map.insert(name, cellkey); + } + Ok(lib) + } + /// Import a [Cell] + fn import_cell(&mut self, pcell: &tproto::Cell) -> LayoutResult { + self.ctx.push(ErrorContext::Cell(pcell.name.clone())); + let mut cell = Cell::new(&pcell.name); + if let Some(ref lay) = pcell.layout { + cell.layout = Some(self.import_layout(lay)?); + } + if let Some(ref a) = pcell.r#abstract { + cell.abs = Some(self.import_abstract(a)?); + } + self.ctx.pop(); + Ok(cell) + } + /// Import an [Abstract] + fn import_abstract(&mut self, pabs: &tproto::Abstract) -> LayoutResult { + self.ctx.push(ErrorContext::Abstract); + + // Convert the abstract's [Outline] + let poutline = self.unwrap(pabs.outline.as_ref(), "Invalid Abstract with no Outline")?; + let (outline, metals) = self.import_outline(poutline)?; + // Create our in-memory [Abstract] + let mut abs = Abstract::new(&pabs.name, metals, outline); + // And convert each of its [Port]s + for pport in &pabs.ports { + abs.ports.push(self.import_abstract_port(pport)?); + } + + self.ctx.pop(); + Ok(abs) + } + /// Import an Abstract [Port] + fn import_abstract_port(&mut self, _pport: &tproto::AbstractPort) -> LayoutResult { + todo!() // FIXME! + } + /// Import an [Outline] + fn import_outline(&mut self, poutline: &tproto::Outline) -> LayoutResult<(Outline, usize)> { + let x = self.import_prim_pitches_list(poutline.x.as_slice(), Dir::Horiz)?; + let y = self.import_prim_pitches_list(poutline.y.as_slice(), Dir::Vert)?; + let metals = usize::try_from(poutline.metals)?; + Ok((Outline::from_prim_pitches(x, y)?, metals)) + } + /// Import an [Assign] + fn import_assignment(&mut self, passn: &tproto::Assign) -> LayoutResult { + let at = self.unwrap(passn.at.as_ref(), "Invalid proto Assign with no location ")?; + let at = self.import_track_cross(at)?; + let assn = Assign::new(passn.net.clone(), at); + Ok(assn) + } + /// Import a [TrackCross] + fn import_track_cross(&mut self, pcross: &tproto::TrackCross) -> LayoutResult { + // Create a [TrackCross], always using `pcross.top` as the primary track, and `pcross.bot` as its intersection. + // First unwrap the not-really-optional `track` and `cross` fields + let track = self.unwrap(pcross.track.as_ref(), "Invalid TrackCross missing `top`")?; + let cross = self.unwrap(pcross.cross.as_ref(), "Invalid TrackCross missing `top`")?; + let track = self.import_track_ref(track)?; + let cross = self.import_track_ref(cross)?; + // And create the intersection + let cross = TrackCross::new(track, cross); + Ok(cross) + } + /// Import a [TrackRef] + fn import_track_ref(&mut self, pref: &tproto::TrackRef) -> LayoutResult { + let layer = usize::try_from(pref.layer)?; + let track = usize::try_from(pref.track)?; + Ok(TrackRef { layer, track }) + } + /// Import a [Layout] + fn import_layout(&mut self, playout: &tproto::Layout) -> LayoutResult { + self.ctx.push(ErrorContext::Impl); + let name = playout.name.clone(); + + let poutline = self.unwrap( + playout.outline.as_ref(), + format!("Invalid tproto::Instance with no Outline: {}", playout.name), + )?; + + let (outline, metals) = self.import_outline(poutline)?; + let mut layout = Layout::new(name, metals, outline); + + for inst in &playout.instances { + layout.instances.push(self.import_instance(inst)?); + } + for s in &playout.assignments { + layout.assignments.push(self.import_assignment(s)?); + } + for txt in &playout.cuts { + layout.cuts.push(self.import_track_cross(txt)?); + } + self.ctx.pop(); + Ok(layout) + } + /// Import an [Instance] + fn import_instance(&mut self, pinst: &tproto::Instance) -> LayoutResult> { + let inst_name = pinst.name.clone(); + self.ctx.push(ErrorContext::Instance(inst_name.clone())); + + // Look up the cell-pointer, which must be imported by now, or we fail + let cell = self.import_reference(&pinst)?; + + // Mostly wind through protobuf-generated structures' layers of [Option]s + let loc = self.unwrap( + pinst.loc.as_ref(), + format!("Invalid tproto::Instance with no Location: {}", pinst.name), + )?; + let loc = self.unwrap( + loc.place.as_ref(), + format!("Invalid tproto::Instance with no Location: {}", pinst.name), + )?; + use tproto::place::Place::{Abs, Rel}; + let loc = match loc { + Abs(ref p) => self.import_xy_prim_pitches(p)?, + Rel(_) => self.fail("Proto-imports of relative placements are not (yet) supported")?, + }; + let loc = Place::Abs(loc); + + let inst = Instance { + inst_name, + cell, + loc, + reflect_horiz: pinst.reflect_horiz, + reflect_vert: pinst.reflect_vert, + }; + let inst = Ptr::new(inst); + self.ctx.pop(); + Ok(inst) + } + /// Import a proto-defined pointer, AKA [tproto::Reference] + fn import_reference(&mut self, pinst: &tproto::Instance) -> LayoutResult> { + // Mostly wind through protobuf-generated structures' layers of [Option]s + let pref = self.unwrap( + pinst.cell.as_ref(), + format!("Invalid tproto::Instance with null Cell: {}", pinst.name), + )?; + let pref_to = self.unwrap( + pref.to.as_ref(), + format!("Invalid tproto::Instance with null Cell: {}", pinst.name), + )?; + use proto::utils::reference::To::{External, Local}; + let cellname: &str = match pref_to { + Local(ref name) => Ok(name), + External(_) => self.fail("Import of external proto-references not supported"), + }?; + // Now look that up in our hashmap + let cellptr = self.unwrap( + self.cell_map.get(cellname), + format!("Instance tproto::Instance of undefined cell {}", cellname), + )?; + Ok(cellptr.clone()) + } + /// Import a [tproto::Point] designed to be interpreted as [PrimPitches] + fn import_xy_prim_pitches(&mut self, pt: &rawproto::Point) -> LayoutResult> { + let x = PrimPitches::x(pt.x.try_into()?); + let y = PrimPitches::y(pt.y.try_into()?); + Ok(Xy::new(x, y)) + } + /// Import a list of primitive-pitch dimensions + fn import_prim_pitches_list( + &mut self, + pts: &[i64], + dir: Dir, + ) -> LayoutResult> { + let mut rv = Vec::with_capacity(pts.len()); + for pt in pts { + rv.push(self.import_prim_pitches(*pt, dir)?); + } + Ok(rv) + } + /// Import a [tproto::Point] designed to be interpreted as [PrimPitches] + fn import_prim_pitches(&mut self, pt: i64, dir: Dir) -> LayoutResult { + Ok(PrimPitches::new(dir, pt.try_into()?)) + } +} // impl ProtoLibImporter + +impl ErrorHelper for ProtoLibImporter { + type Error = LayoutError; + fn err(&self, msg: impl Into) -> LayoutError { + LayoutError::Import { + message: msg.into(), + stack: self.ctx.clone(), + } + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::SerializationFormat::Yaml; + + #[test] + fn proto_roundtrip1() -> LayoutResult<()> { + // Proto-export + let lib = Library::new("proto_rt1"); + let plib = ProtoExporter::export(&lib)?; + assert_eq!(plib.domain, "proto_rt1"); + assert_eq!(plib.cells, Vec::new()); + assert_eq!(plib.author, None); + + // Import it back + let lib2 = ProtoLibImporter::import(&plib)?; + assert_eq!(lib2.name, "proto_rt1"); + assert_eq!(lib2.cells.len(), 0); + assert_eq!(lib2.rawlibs.len(), 0); + + Ok(()) + } + + #[test] + fn proto_roundtrip2() -> LayoutResult<()> { + // Proto round-trip, round 2, with some content + let mut lib = Library::new("proto_rt2"); + let mut cell = Cell::new("proto_rt2_cell"); + cell.layout = Some(Layout::new("proto_rt2_cell", 0, Outline::rect(1, 1)?)); + cell.abs = Some(Abstract::new("proto_rt2_cell", 0, Outline::rect(1, 1)?)); + lib.cells.add(cell); + + let plib = ProtoExporter::export(&lib)?; + assert_eq!(plib.domain, "proto_rt2"); + assert_eq!(plib.author, None); + assert_eq!(plib.cells.len(), 1); + let pcell = &plib.cells[0]; + let playout = pcell.layout.as_ref().unwrap(); + assert_eq!(playout.name, "proto_rt2_cell"); + let playout_outline = playout.outline.as_ref().unwrap(); + assert_eq!(playout_outline.x, vec![1]); + assert_eq!(playout_outline.y, vec![1]); + let pabs = pcell.r#abstract.as_ref().unwrap(); + assert_eq!(pabs.name, "proto_rt2_cell"); + assert_eq!(pabs.ports.len(), 0); + let pabs_outline = pabs.outline.as_ref().unwrap(); + assert_eq!(pabs_outline.x, vec![1]); + assert_eq!(pabs_outline.y, vec![1]); + assert_eq!(pabs_outline, playout_outline); + + // Import it back + let lib2 = ProtoLibImporter::import(&plib)?; + assert_eq!(lib2.name, "proto_rt2"); + assert_eq!(lib2.rawlibs.len(), 0); + assert_eq!(lib2.cells.len(), 1); + let cell2 = &lib2.cells[0].read()?; + let layout2 = cell2.layout.as_ref().unwrap(); + assert_eq!(layout2.name, "proto_rt2_cell"); + assert_eq!(layout2.outline, Outline::rect(1, 1)?); + assert_eq!(layout2.metals, 0); + assert_eq!(layout2.instances.len(), 0); + assert_eq!(layout2.assignments.len(), 0); + assert_eq!(layout2.cuts.len(), 0); + let abs2 = cell2.abs.as_ref().unwrap(); + assert_eq!(abs2.name, "proto_rt2_cell"); + assert_eq!(abs2.ports.len(), 0); + + // Yaml.save(&plib, "proto_rt2.yaml")?; + Ok(()) + } + + #[test] + fn proto_yaml1() -> LayoutResult<()> { + // Proto export, then YAML export + let lib = Library::new("proto_yaml1"); + let plib = ProtoExporter::export(&lib)?; + assert_eq!(plib.domain, "proto_yaml1"); + assert_eq!(plib.cells, Vec::new()); + assert_eq!(plib.author, None); + + // Yaml.save(&plib, "proto_yaml1.yaml")?; + Ok(()) + } + #[test] + fn proto_yaml2() -> LayoutResult<()> { + // Import from YAML + let yaml = r#" +--- +domain: proto_rt2 +cells: + - name: proto_rt2_cell + abstract: + name: proto_rt2_cell + outline: + x: [ 1 ] + y: [ 1 ] + metals: 0 + ports: [] + layout: + name: proto_rt2_cell + outline: + x: [ 1 ] + y: [ 1 ] + metals: 0 + instances: [] + assignments: [] + cuts: [] +"#; + let plib: tproto::Library = Yaml.from_str(yaml)?; + let lib = ProtoLibImporter::import(&plib)?; + + // let lib = Library::new("proto_yaml1"); + // let plib = ProtoExporter::export(&lib)?; + // assert_eq!(plib.domain, "proto_yaml1"); + // assert_eq!(plib.cells, Vec::new()); + // assert_eq!(plib.author, None); + + // Yaml.save(&plib, "proto_yaml2.yaml")?; + Ok(()) + } +} diff --git a/Tetris/tetris/conv/raw.rs b/Tetris/tetris/conv/raw.rs new file mode 100644 index 0000000..bfeb49b --- /dev/null +++ b/Tetris/tetris/conv/raw.rs @@ -0,0 +1,851 @@ +//! +//! # Raw-Layout Conversion Module +//! + +// Std-lib +use std::collections::HashMap; +use std::convert::TryFrom; +use std::fmt::Debug; + +// Crates.io +use slotmap::{new_key_type, SlotMap}; + +// Local imports +use crate::{ + abs, cell, + coords::{DbUnits, HasUnits, PrimPitches, UnitSpeced, Xy}, + instance::Instance, + layout::Layout, + library::Library, + outline::Outline, + raw::{self, Dir, LayoutError, LayoutResult, Point}, + stack::{LayerPeriod, RelZ}, + tracks::{Track, TrackCross, TrackSegmentType}, + utils::{ErrorContext, ErrorHelper, Ptr, PtrList}, + validate, +}; + +// Create key-types for each internal type stored in [SlotMap]s +new_key_type! { + /// Keys for [ValidAssign] entries + pub struct AssignKey; +} +/// A short-lived Cell, largely organized by layer +#[derive(Debug, Clone)] +struct TempCell<'lib> { + /// Reference to the source [Cell] + cell: &'lib Layout, + /// Reference to the source [Library] + lib: &'lib Library, + /// Instances and references to their definitions + instances: PtrList, + /// Cuts, arranged by Layer + cuts: Vec>, + /// Validated Assignments + assignments: SlotMap, + /// Assignments, arranged by Layer + top_assns: Vec>, + /// Assignments, arranged by Layer + bot_assns: Vec>, +} +/// Temporary arrangement of data for a [Layer] within a [Cell] +#[derive(Debug, Clone)] +struct TempCellLayer<'lib> { + /// Reference to the validated metal layer + layer: &'lib validate::ValidMetalLayer, + /// Reference to the parent cell + cell: &'lib TempCell<'lib>, + /// Instances which intersect with this layer and period + instances: PtrList, + /// Pitch per layer-period + pitch: DbUnits, + /// Number of layer-periods + nperiods: usize, + /// Spanning distance in the layer's "infinite" dimension + span: DbUnits, +} + +/// Short-Lived structure of the stuff relevant for converting a single LayerPeriod, +/// on a single Layer, in a single Cell. +#[derive(Debug, Clone)] +struct TempPeriod<'lib> { + periodnum: usize, + cell: &'lib TempCell<'lib>, + layer: &'lib TempCellLayer<'lib>, + /// Instance Blockages + blockages: Vec<(PrimPitches, PrimPitches, Ptr)>, + cuts: Vec<&'lib TrackCross>, + top_assns: Vec, + bot_assns: Vec, +} +/// # Converter from [Library] and constituent elements to [raw::Library] +#[derive(Debug)] +pub struct RawExporter { + /// Source [Library] + lib: Library, + /// Source (validated) [Stack] + stack: validate::ValidStack, + /// HashMap from source [Cell] to exported [raw::Cell], + /// largely for lookup during conversion of [Instance]s + rawcells: HashMap, Ptr>, + /// Context stack, largely for error reporting + ctx: Vec, +} +impl<'lib> RawExporter { + /// Convert the combination of a [Library] `lib` and [Stack] `stack` to a [raw::Library]. + /// Both `lib` and `stack` are consumed in the process. + pub fn convert(lib: Library, stack: validate::ValidStack) -> LayoutResult> { + // Put the combination through absolute-placement + use crate::placer::Placer; + let (lib, stack) = Placer::place(lib, stack)?; + + // Run the [Library] through validation + validate::LibValidator::new(&stack).validate_lib(&lib)?; + + let mut myself = Self { + lib, + stack, + rawcells: HashMap::new(), + ctx: Vec::new(), + }; + myself.export_stack()?; + myself.export_lib() + } + /// "Convert" our [Stack]. Really just checks a few properties are valid. + fn export_stack(&mut self) -> LayoutResult<()> { + // Require our [Stack] specify both: + // (a) set of [raw::Layers], and + // (b) a boundary layer + // Both these fields are frequently `unwrap`ed hereafter. + if !self.stack.rawlayers.is_some() { + return self.fail("Raw export failed: no [raw::Layers] specified"); + } + if !self.stack.boundary_layer.is_some() { + return self.fail("Raw export failed: no `boundary_layer` specified"); + } + Ok(()) + } + /// Convert everything in our [Library] + fn export_lib(&mut self) -> LayoutResult> { + self.ctx.push(ErrorContext::Library(self.lib.name.clone())); + // Get our starter raw-lib, either anew or from any we've imported + let rawlibptr = if self.lib.rawlibs.len() == 0 { + // Create a new [raw::Library] + let mut rawlib = raw::Library::new( + &self.lib.name, + self.stack.units, + Some(Ptr::clone(self.stack.rawlayers.as_ref().unwrap())), + ); + Ok(Ptr::new(rawlib)) + } else if self.lib.rawlibs.len() == 1 { + // Pop the sole raw-library, and use it as a starting point + let rawlibptr = self.lib.rawlibs.pop().unwrap(); + let mut rawlib = rawlibptr.write()?; + rawlib.name = self.lib.name.to_string(); + if rawlib.units != self.stack.units { + // Check that the units match, or fail + return self.fail(format!( + "NotImplemented: varying units between raw and tetris libraries: {:?} vs {:?}", + rawlib.units, self.stack.units, + )); + } + drop(rawlib); + Ok(rawlibptr) + } else { + // Multiple raw-libraries will require some merging + // Probably not difficult, but not done yet either. + self.fail("NotImplemented: with multiple [raw::Library") + }?; + { + // Get write-access to the raw-lib + let mut rawlib = rawlibptr.write()?; + // Convert each defined [Cell] to a [raw::Cell] + for srcptr in self.lib.dep_order() { + let rawptr = self.export_cell(&*srcptr.read()?, &mut rawlib.cells)?; + self.rawcells.insert(srcptr.clone(), rawptr); + } + } // Ends `rawlib` write-access scope + self.ctx.pop(); + Ok(rawlibptr) + } + /// Convert a [Cell] to a [raw::Cell] and add to `rawcells`. + /// FIXME: In reality only one of the cell-views is converted, + /// generally the "most specific" available view. + fn export_cell( + &mut self, + cell: &cell::Cell, + rawcells: &mut PtrList, + ) -> LayoutResult> { + if let Some(ref x) = cell.raw { + // Raw definitions store the cell-pointer + // Just return a copy of it and *don't* add it to `rawcells` + // First check for validity, i.e. lack of alternate definitions + if cell.abs.is_some() || cell.layout.is_some() { + // FIXME: move this to validation stages + return self.fail(format!( + "Cell {} has an invalid combination of raw-definition and tetris-definition", + cell.name, + )); + } + return Ok(x.cell.clone()); + } + if cell.abs.is_none() && cell.layout.is_none() { + // FIXME: move this to validation stages + return self.fail(format!( + "Cell {} has no abstract nor implementation", + cell.name, + )); + } + + // Create the raw-cell + let mut rawcell = raw::Cell::new(&cell.name.to_string()); + // And create each defined view + if let Some(ref x) = cell.layout { + rawcell.layout = Some(self.export_layout_impl(x)?); + } + if let Some(ref x) = cell.abs { + rawcell.abs = Some(self.export_abstract(x)?); + } + // Add it to `rawcells`, and return the pointer that comes back + Ok(rawcells.add(rawcell)) + } + /// Convert to a raw layout cell + fn export_layout_impl(&self, layout: &Layout) -> LayoutResult { + if layout.outline.x.len() > 1 { + return Err(LayoutError::Str( + "Non-rectangular outline; conversions not supported (yet)".into(), + )); + }; + let mut elems: Vec = Vec::new(); + // Re-organize the cell into the format most helpful here + let temp_cell = self.temp_cell(layout)?; + // Convert a layer at a time, starting from bottom + for layernum in 0..layout.metals { + // Organize the cell/layer combo into temporary conversion format + let temp_layer = self.temp_cell_layer(&temp_cell, self.stack.metal(layernum)?)?; + // Convert each "layer period" one at a time + for periodnum in 0..temp_layer.nperiods { + // Again, re-organize into the relevant objects for this "layer period" + let temp_period = self.temp_cell_layer_period(&temp_layer, periodnum)?; + // And finally start doing stuff! + elems.extend(self.export_cell_layer_period(&temp_period)?); + } + } + // Convert our [Outline] into a polygon + elems.push(self.export_outline(&layout.outline)?); + // Convert our [Instance]s + let insts = layout + .instances + .iter() + .map(|ptr| { + let inst = ptr.read()?; + self.export_instance(&*inst) + }) + .collect::, _>>()?; + // Aaaand create our new [raw::Cell] + Ok(raw::Layout { + name: layout.name.clone(), + insts, + elems, + ..Default::default() + }) + } + /// Convert an [Instance] to a [raw::Instance] + fn export_instance(&self, inst: &Instance) -> LayoutResult { + // Get the raw-cell pointer from our mapping. + // Note this requires dependent cells be converted first, depth-wise. + let rawkey = self.unwrap( + self.rawcells.get(&inst.cell), + format!("Internal Error Exporting Instance {}", inst.inst_name), + )?; + // Convert its orientation + let (reflect_vert, angle) = match (inst.reflect_vert, inst.reflect_horiz) { + (false, false) => (false, None), // Default orientation + (true, false) => (true, None), // Flip vertically + (false, true) => (true, Some(180.)), // Flip horiz via vert-flip and rotation + (true, true) => (false, Some(180.)), // Flip both by rotation + }; + // Primarily scale the location of each instance by our pitches + Ok(raw::Instance { + inst_name: inst.inst_name.clone(), + cell: rawkey.clone(), + loc: self.export_xy(inst.loc.abs()?).into(), + reflect_vert, + angle, + }) + } + /// Create a [TempCell], organizing [Cell] data in more-convenient fashion for conversion + fn temp_cell<'a>(&'a self, layout: &'a Layout) -> LayoutResult> { + // Collect references to its instances + let instances = layout.instances.clone(); + // Validate `cuts`, and arrange them by layer + let mut cuts: Vec> = vec![vec![]; layout.metals]; + for cut in layout.cuts.iter() { + validate::LibValidator::new(&self.stack).validate_track_cross(cut)?; + cuts[cut.track.layer].push(&cut); + // FIXME: cell validation should also check that this lies within our outline. probably do this earlier + } + + // Validate all the cell's assignments, and arrange references by layer + let mut bot_assns = vec![vec![]; layout.metals]; + let mut top_assns = vec![vec![]; layout.metals]; + let mut assignments = SlotMap::with_key(); + for assn in layout.assignments.iter() { + // Validate the assignment + let v = validate::LibValidator::new(&self.stack).validate_assign(assn)?; + let bot = v.bot.layer; + let top = v.top.layer; + + // Check both layers exist in our stack + // (This also returns the layer, which we ignore.) + self.stack.metal(bot)?; + self.stack.metal(top)?; + + let k = assignments.insert(v); + bot_assns[bot].push(k); + top_assns[top].push(k); + } + // And create our (temporary) cell data! + Ok(TempCell { + cell: layout, + lib: &self.lib, + instances, + assignments, + top_assns, + bot_assns, + cuts, + }) + } + /// Convert a single row/col (period) on a single layer in a single Cell. + fn export_cell_layer_period( + &self, + temp_period: &TempPeriod, + ) -> LayoutResult> { + let mut elems: Vec = Vec::new(); + let layer = temp_period.layer.layer; // FIXME! Can't love this name. + + // Create the layer-period object we'll manipulate most of the way + let mut layer_period = temp_period + .layer + .layer + .spec + .to_layer_period(temp_period.periodnum, temp_period.layer.span.0)?; + // Insert blockages on each track + for (n1, n2, inst_ptr) in temp_period.blockages.iter() { + // Convert primitive-pitch-based blockages to db units + let start = self.db_units(*n1); + let stop = self.db_units(*n2); + let res = layer_period.block(start, stop, &inst_ptr); + self.ok( + res, + format!( + "Could not insert blockage on Layer {:?}, period {} from {:?} to {:?}", + layer, temp_period.periodnum, start, stop + ), + )?; + } + // Place all relevant cuts + let nsig = layer_period.signals.len(); + for cut in temp_period.cuts.iter() { + // Cut the assigned track + let track = &mut layer_period.signals[cut.track.track % nsig]; + let cut_loc = self.track_cross_xy(cut)?; + let dist = cut_loc[layer.spec.dir]; + let res = track.cut( + dist - layer.spec.cutsize / 2, // start + dist + layer.spec.cutsize / 2, // stop + cut, // src + ); + self.ok( + res, + format!("Could not make track-cut {:?} in {:?}", cut, temp_period), + )?; + } + // Handle Net Assignments + // Start with those for which we're the lower of the two layers. + // These will also be where we add vias. + let mut via_opt = None; + for assn_id in temp_period.bot_assns.iter() { + // Note that while `via_layer` is identical over every iteration of this loop, it may not exist if we never enter the loop. + // So, retrieve it from the `stack` on our first iteration. + if via_opt.is_none() { + via_opt = Some(self.stack.via_from(layer.index)?); + } + let via_layer = via_opt.as_ref().unwrap(); + + let assn = self.unwrap( + temp_period.cell.assignments.get(*assn_id), + "Internal error: invalid assignment", + )?; + self.assign_track(layer, &mut layer_period, assn, false)?; + let assn_loc = self.track_cross_xy(&assn.src.at)?; + // Create the via element + let e = raw::Element { + net: Some(assn.src.net.clone()), + layer: via_layer.raw.unwrap(), + purpose: raw::LayerPurpose::Drawing, + inner: raw::Shape::Rect(raw::Rect { + p0: self.export_point( + assn_loc.x - via_layer.size.x / 2, + assn_loc.y - via_layer.size.y / 2, + ), + p1: self.export_point( + assn_loc.x + via_layer.size.x / 2, + assn_loc.y + via_layer.size.y / 2, + ), + }), + }; + elems.push(e); + } + + // Assign all the segments for which we're the top layer + for assn_id in temp_period.top_assns.iter() { + let assn = self.unwrap( + temp_period.cell.assignments.get(*assn_id), + "Internal error: invalid assignment", + )?; + self.assign_track(layer, &mut layer_period, assn, true)?; + } + + // Convert all TrackSegments to raw Elements + for t in layer_period.rails.iter() { + elems.extend(self.export_track(t, &layer)?); + } + for t in layer_period.signals.iter() { + elems.extend(self.export_track(t, &layer)?); + } + Ok(elems) + } + /// Set the net corresponding to `assn` on layer `layer`. + /// + /// The type signature, particularly lifetimes, aren't pretty. + /// <'f> is the short "function lifetime" of the argument references + pub fn assign_track<'f>( + &self, + layer: &'f validate::ValidMetalLayer, + layer_period: &'f mut LayerPeriod<'lib>, + assn: &'lib validate::ValidAssign, + top: bool, // Boolean indication of whether to assign `top` or `bot`. FIXME: not our favorite. + ) -> LayoutResult<()> { + // Grab a (mutable) reference to the assigned track + let nsig = layer_period.signals.len(); + let track = if top { assn.top.track } else { assn.bot.track }; + let track = &mut layer_period.signals[track % nsig]; + // And set the net at the assignment's location + let assn_loc = self.track_cross_xy(&assn.src.at)?; + let res = track.set_net(assn_loc[layer.spec.dir], &assn.src); + self.ok(res, "Error Assigning Track")?; + Ok(()) + } + /// Convert a [Abstract] into raw form. + pub fn export_abstract(&mut self, abs: &abs::Abstract) -> LayoutResult { + self.ctx.push(ErrorContext::Abstract); + + // Create the outline-element, and grab a copy of its inner shape + let outline = self.export_outline(&abs.outline)?; + let outline_shape = outline.inner.clone(); + // Create the raw abstract + let mut rawabs = raw::Abstract::new(&abs.name, outline); + + // Draw a blockage on each layer, equal to the shape of the outline + for layerindex in 0..abs.metals { + let layerkey = self.stack.metal(layerindex)?.raw.unwrap(); + let blk = vec![outline_shape.clone()]; + rawabs.blockages.insert(layerkey, blk); + } + + // Create shapes for each port + for port in abs.ports.iter() { + let rawport = self.export_abstract_port(abs, port)?; + rawabs.ports.push(rawport); + } + // And return the [raw::Abstract] + self.ctx.pop(); + Ok(rawabs) + } + /// Convert an [abs::Port] into raw form. + pub fn export_abstract_port( + &mut self, + abs: &abs::Abstract, + port: &abs::Port, + ) -> LayoutResult { + use abs::PortKind::{Edge, ZTopEdge, ZTopInner}; + + let (layerkey, shape): (raw::LayerKey, raw::Shape) = match &port.kind { + Edge { + layer: layer_index, + track, + side, + } => { + let layer = &self.stack.metal(*layer_index)?.spec; + // First get the "infinite dimension" coordinate from the edge + let infdims: (DbUnits, DbUnits) = match side { + abs::Side::BottomOrLeft => (DbUnits(0), DbUnits(100)), + abs::Side::TopOrRight => { + // FIXME: this assumes rectangular outlines; will take some more work for polygons. + let outside = self.db_units(abs.outline.max(layer.dir)); + (outside - DbUnits(100), outside) + } + }; + // Now get the "periodic dimension" from our layer-center + let perdims: (DbUnits, DbUnits) = self.track_span(*layer_index, *track)?; + // Presuming we're horizontal, points are here: + let mut pts = [Xy::new(infdims.0, perdims.0), Xy::new(infdims.1, perdims.1)]; + // And if vertical, just transpose them + if layer.dir == Dir::Vert { + pts[0] = pts[0].transpose(); + pts[1] = pts[1].transpose(); + } + ( + self.stack.metal(*layer_index)?.raw.unwrap(), + raw::Shape::Rect(raw::Rect { + p0: self.export_xy(&pts[0]), + p1: self.export_xy(&pts[1]), + }), + ) + } + ZTopEdge { track, side, into } => { + let top_metal = if abs.metals == 0 { + self.fail("Abs Port with no metal layers") + } else { + Ok(abs.metals - 1) + }?; + let layer = &self.stack.metal(top_metal)?.spec; + let other_layer_index = match into.1 { + RelZ::Above => top_metal + 1, + RelZ::Below => top_metal - 1, + }; + let other_layer = self.stack.metal(other_layer_index)?; + let other_layer_center = other_layer.center(into.0)?; + // First get the "infinite dimension" coordinate from the edge + let infdims: (DbUnits, DbUnits) = match side { + abs::Side::BottomOrLeft => (DbUnits(0), other_layer_center), + abs::Side::TopOrRight => { + // FIXME: this assumes rectangular outlines; will take some more work for polygons. + let outside = self.db_units(abs.outline.max(layer.dir)); + (other_layer_center, outside) + } + }; + // Now get the "periodic dimension" from our layer-center + let perdims: (DbUnits, DbUnits) = self.track_span(top_metal, *track)?; + // Presuming we're horizontal, points are here: + let mut pts = [Xy::new(infdims.0, perdims.0), Xy::new(infdims.1, perdims.1)]; + // And if vertical, just transpose them + if layer.dir == Dir::Vert { + pts[0] = pts[0].transpose(); + pts[1] = pts[1].transpose(); + } + ( + self.stack.metal(top_metal)?.raw.unwrap(), + raw::Shape::Rect(raw::Rect { + p0: self.export_xy(&pts[0]), + p1: self.export_xy(&pts[1]), + }), + ) + } + ZTopInner { .. } => todo!(), + }; + let mut shapes = HashMap::new(); + shapes.insert(layerkey, vec![shape]); + let rawport = raw::AbstractPort { + net: port.name.clone(), + shapes, + }; + Ok(rawport) + } + /// Get the positions spanning track number `track` on layer number `layer` + fn track_span( + &self, + layer_index: usize, + track_index: usize, + ) -> LayoutResult<(DbUnits, DbUnits)> { + let layer = self.stack.metal(layer_index)?; + layer.span(track_index) + } + /// Convert an [Outline] to a [raw::Shape] + fn outline_shape(&self, outline: &Outline) -> LayoutResult { + // FIXME: always uses `Poly`, because some proto-schemas insist on it as the most general. + // Probably move that conversion down-stack, keep either `Poly` or `Rect` on `layout21::raw::Abstract`. + + // if outline.x.len() == 1 { + // // Rectangular + // let p0 = Point::new(0, 0); + // let xp = self.db_units(outline.x[0]).raw(); + // let yp = self.db_units(outline.y[0]).raw(); + // let p1 = Point::new(xp, yp); + // return Ok(raw::Shape::Rect { p0, p1 }); + // } + // Polygon + // Create an array of Outline-Points + let mut pts = vec![Point { x: 0, y: 0 }]; + let mut xp: isize; + let mut yp: isize = 0; + for i in 0..outline.x.len() { + xp = self.db_units(outline.x[i]).raw(); + pts.push(Point::new(xp, yp)); + yp = self.db_units(outline.y[i]).raw(); + pts.push(Point::new(xp, yp)); + } + // Add the final implied Point at (x, y[-1]) + pts.push(Point::new(0, yp)); + Ok(raw::Shape::Polygon(raw::Polygon { points: pts })) + } + /// Convert an [Outline] to a [raw::Element] polygon + pub fn export_outline(&self, outline: &Outline) -> LayoutResult { + // Create the outline shape + let shape = self.outline_shape(outline)?; + // And create the [raw::Element] + Ok(raw::Element { + net: None, + layer: self.stack.boundary_layer.unwrap(), + purpose: raw::LayerPurpose::Outline, + inner: shape, + }) + } + /// Convert a [Track]-full of [TrackSegment]s to a vector of [raw::Element] rectangles + fn export_track( + &self, + track: &Track, + layer: &validate::ValidMetalLayer, + ) -> LayoutResult> { + let mut elems = Vec::new(); + for seg in &track.segments { + // Convert wires and rails, skip blockages and cuts + use TrackSegmentType::*; + let net: Option = match seg.tp { + Wire { src } => src.map(|src| src.net.clone()), + Rail(rk) => Some(rk.to_string()), + Cut { .. } | Blockage { .. } => continue, + }; + // Convert the inner shape + let inner = match track.data.dir { + Dir::Horiz => raw::Shape::Rect(raw::Rect { + p0: self.export_point(seg.start, track.data.start), + p1: self.export_point(seg.stop, track.data.start + track.data.width), + }), + Dir::Vert => raw::Shape::Rect(raw::Rect { + p0: self.export_point(track.data.start, seg.start), + p1: self.export_point(track.data.start + track.data.width, seg.stop), + }), + }; + // And pack it up as a [raw::Element] + let e = raw::Element { + net, + layer: self.stack.metal(layer.index)?.raw.unwrap(), + purpose: raw::LayerPurpose::Drawing, + inner, + }; + elems.push(e); + } + Ok(elems) + } + /// Create a [TempCellLayer] for the intersection of `temp_cell` and `layer` + fn temp_cell_layer<'a>( + &self, + temp_cell: &'a TempCell, + layer: &'a validate::ValidMetalLayer, + ) -> LayoutResult> { + // Sort out which of the cell's [Instance]s come up to this layer + let mut instances = Vec::with_capacity(temp_cell.instances.len()); + for ptr in temp_cell.instances.iter() { + let inst = ptr.read()?; + let cell = inst.cell.read()?; + if cell.metals()? > layer.index { + instances.push(ptr.clone()); + } + } + // And convert it to a [PtrList] + let instances = PtrList::from_ptrs(instances); + + // Sort out which direction we're working across + let cell = temp_cell.cell; + // Convert to database units + let x = self.db_units(cell.outline.x[0]); // FIXME: rectangles implied here + let y = self.db_units(cell.outline.y[0]); + let (span, breadth) = match layer.spec.dir { + Dir::Horiz => (x, y), + Dir::Vert => (y, x), + }; + + // FIXME: move to `validate` stage + if (breadth % layer.pitch) != 0 { + return self.fail(format!( + "{} has invalid dimension on {}: {:?}, must be multiple of {:?}", + cell.name, layer.spec.name, breadth, layer.pitch, + )); + } + let nperiods = usize::try_from(breadth / layer.pitch).unwrap(); // FIXME: errors + Ok(TempCellLayer { + layer, + cell: temp_cell, + instances, + nperiods, + pitch: layer.pitch, + span, + }) + } + /// Create the [TempPeriod] at the intersection of `temp_layer` and `periodnum` + fn temp_cell_layer_period<'a>( + &self, + temp_layer: &'a TempCellLayer, + periodnum: usize, + ) -> LayoutResult> { + let cell = temp_layer.cell; + let layer = temp_layer.layer; + let dir = layer.spec.dir; + + // For each row, decide which instances intersect + // Convert these into blockage-areas for the tracks + let mut blockages = Vec::with_capacity(temp_layer.instances.len()); + for ptr in temp_layer.instances.iter() { + let inst = &*ptr.read()?; + if self.instance_intersects(inst, layer, periodnum)? { + // Create the blockage + let cell = inst.cell.read()?; + let start = inst.loc.abs()?[dir]; + let stop = start + cell.outline()?.max(dir); + blockages.push((start, stop, ptr.clone())); + } + } + + // Grab indices of the relevant tracks for this period + let nsig = temp_layer.layer.period_data.signals.len(); + let relevant_track_nums = (periodnum * nsig, (periodnum + 1) * nsig); + // Filter cuts down to those in this period + let cuts: Vec<&TrackCross> = cell.cuts[temp_layer.layer.index] + .iter() + .filter(|cut| { + cut.track.track >= relevant_track_nums.0 && cut.track.track < relevant_track_nums.1 + }) + .map(|r| *r) + .collect(); + // Filter assignments down to those in this period + let top_assns = cell.top_assns[temp_layer.layer.index] + .iter() + .filter(|id| { + let assn = cell + .assignments + .get(**id) + .ok_or(LayoutError::from("Internal error: invalid assignment")) + .unwrap(); + assn.top.track >= relevant_track_nums.0 && assn.top.track < relevant_track_nums.1 + }) + .copied() + .collect(); + let bot_assns = cell.bot_assns[temp_layer.layer.index] + .iter() + .filter(|id| { + let assn = cell + .assignments + .get(**id) + .ok_or(LayoutError::from("Internal error: invalid assignment")) + .unwrap(); + assn.bot.track >= relevant_track_nums.0 && assn.bot.track < relevant_track_nums.1 + }) + .copied() + .collect(); + + Ok(TempPeriod { + periodnum, + cell, + layer: temp_layer, + blockages, + cuts, + top_assns, + bot_assns, + }) + } + /// Boolean indication of whether `inst` intersects `layer` at `periodnum` + /// FIXME: rectangular only for now + fn instance_intersects( + &self, + inst: &Instance, + layer: &validate::ValidMetalLayer, + periodnum: usize, + ) -> LayoutResult { + // Grab the layer's *periodic* direction + let dir = !layer.spec.dir; + // Get its starting location in that dimension + let inst_start = self.db_units(inst.loc.abs()?[dir]); + // Sort out whether it's been reflected in this direction + let reflected = match dir { + Dir::Horiz => inst.reflect_horiz, + Dir::Vert => inst.reflect_vert, + }; + // Grab the span of the cell-outline + let span = { + let cell = inst.cell.read()?; + self.db_units(cell.outline()?.max(dir)) + }; + // And sort out the span of the [Instance], from its cell-outline and reflection + let (inst_min, inst_max) = if !reflected { + (inst_start, inst_start + span) + } else { + (inst_start - span, inst_start) + }; + // And return the boolean intersection. "Touching" edge-to-edge is *not* considered an intersection. + Ok(inst_max > layer.pitch * periodnum && inst_min < layer.pitch * (periodnum + 1)) + } + /// Convert any [UnitSpeced]-convertible distances into [DbUnits] + fn db_units(&self, pt: impl Into) -> DbUnits { + let pt: UnitSpeced = pt.into(); + match pt { + UnitSpeced::DbUnits(u) => u, // Return as-is + UnitSpeced::PrimPitches(p) => { + // Multiply by the primitive pitch in `pt`s direction + let pitch = self.stack.prim.pitches[p.dir]; + (p.num * pitch.raw()).into() + } + UnitSpeced::LayerPitches(_p) => { + // LayerPitches are always in the layer's "periodic" dimension + todo!() + } + } + } + /// Convert an [Xy] into a [raw::Point] + fn export_xy>(&self, xy: &Xy) -> raw::Point { + let x = self.db_units(xy.x); + let y = self.db_units(xy.y); + self.export_point(x, y) + } + /// Convert a two-tuple of [DbUnits] into a [raw::Point] + fn export_point(&self, x: DbUnits, y: DbUnits) -> raw::Point { + raw::Point::new(x.0, y.0) + } + /// Convert a [TrackCross] into an (x,y) ([Xy]) coordinate in [DbUnits] + fn track_cross_xy(&self, i: &TrackCross) -> LayoutResult> { + // Find the (x,y) center of our track, initially assuming it runs vertically + let x = self.stack.metal(i.track.layer)?.center(i.track.track)?; + let y = self.stack.metal(i.cross.layer)?.center(i.cross.track)?; + + // And transpose if it's actually horizontal + let mut xy = Xy::new(x, y); + if self.stack.metal(i.track.layer)?.spec.dir == Dir::Horiz { + xy = xy.transpose(); + } + Ok(xy) + } +} +impl ErrorHelper for RawExporter { + type Error = LayoutError; + fn err(&self, msg: impl Into) -> LayoutError { + LayoutError::Export { + message: msg.into(), + stack: self.ctx.clone(), + } + } + fn ok( + &self, + res: Result, + msg: impl Into, + ) -> Result { + match res { + Ok(t) => Ok(t), + Err(e) => Err(LayoutError::Conversion { + message: msg.into(), + err: Box::new(e), + stack: self.ctx.clone(), + }), + } + } +} diff --git a/Tetris/tetris/coords.rs b/Tetris/tetris/coords.rs new file mode 100644 index 0000000..5dd0d59 --- /dev/null +++ b/Tetris/tetris/coords.rs @@ -0,0 +1,359 @@ +//! +//! # Tetris Coordinate System(s) +//! + +// Std-lib imports +use std::convert::TryFrom; +use std::fmt::Debug; + +// Crates.io +use derive_more::{Add, AddAssign, DivAssign, From, MulAssign, Sub, SubAssign, Sum}; +use serde::{Deserialize, Serialize}; + +// Local imports +use crate::raw::Dir; + +/// # Location Integer Type-Alias +/// +/// Many internal fields are conceptually unsigned integers, but also undergo lots of math. +/// Rather than converting at each call-site, most are converted to [Int] and value-checked at creation time. +/// +/// Unsigned integers ([usize]) are generally used for indices, such as where the [Index] trait accepts them. +pub type Int = isize; + +/// # Unit-Specified Distances Enumeration +/// +/// Much of the confusion in a multi-coordinate system such as this +/// lies in keeping track of which numbers are in which units. +/// +/// There are three generally useful units of measure here: +/// * DB Units generally correspond to physical length quantities, e.g. nanometers +/// * Primitive pitches +/// * Per-layer pitches, parameterized by a metal-layer index +/// +#[derive(From, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum UnitSpeced { + /// Database Units, e.g. nanometers + DbUnits(DbUnits), + /// Primitive Pitches, parameterized by direction + PrimPitches(PrimPitches), + /// Per-Layer Pitches, parameterized by a metal-layer index + LayerPitches(LayerPitches), +} +/// # HasUnits +/// +/// A trait for types that have a unit-speced value. +/// Largely synonymous with being a variant of [UnitSpeced]. +/// +pub trait HasUnits: Clone + Copy { + /// Retrieve the raw number being spec'ed. Used sparingly, e.g. for exports. + fn raw(&self) -> Int; +} +impl HasUnits for UnitSpeced { + /// Dispatch the `raw` method to each variant + fn raw(&self) -> Int { + match self { + UnitSpeced::DbUnits(x) => x.raw(), + UnitSpeced::PrimPitches(x) => x.raw(), + UnitSpeced::LayerPitches(x) => x.raw(), + } + } +} + +/// A Scalar Value in Database Units +#[derive( + From, + Add, + AddAssign, + Sub, + SubAssign, + MulAssign, + DivAssign, + Sum, + Debug, + Default, + Clone, + Copy, + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, +)] +pub struct DbUnits(pub Int); +impl HasUnits for DbUnits { + /// Every so often we need the raw number, fine. Use sparingly. + #[inline(always)] + fn raw(&self) -> Int { + self.0 + } +} +impl std::ops::Div for DbUnits { + type Output = Int; + fn div(self, rhs: DbUnits) -> Self::Output { + self.raw() / rhs.raw() + } +} +impl std::ops::Div for DbUnits { + type Output = Self; + fn div(self, rhs: Int) -> Self::Output { + Self(self.raw() / rhs) + } +} +impl std::ops::Rem for DbUnits { + type Output = Int; + fn rem(self, rhs: DbUnits) -> Self::Output { + self.raw().rem(rhs.raw()) + } +} +impl std::ops::Mul for DbUnits { + type Output = Self; + fn mul(self, rhs: Int) -> Self::Output { + Self(self.0 * rhs) + } +} +impl std::ops::Mul for DbUnits { + type Output = Self; + fn mul(self, rhs: usize) -> Self::Output { + Self(Int::try_from(rhs).unwrap() * self.0) + } +} + +/// A Scalar Value in Primitive-Pitches +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub struct PrimPitches { + pub dir: Dir, + pub num: Int, +} +impl PrimPitches { + /// Create a new [PrimPitches] + pub fn new(dir: Dir, num: Int) -> Self { + Self { dir, num } + } + /// Create a [PrimPitches] in the `x` direction + pub fn x(num: Int) -> Self { + Self::new(Dir::Horiz, num) + } + /// Create a [PrimPitches] in the `y` direction + pub fn y(num: Int) -> Self { + Self::new(Dir::Vert, num) + } + /// Create a new [PrimPitches] with opposite sign of `self.num` + pub fn negate(&self) -> Self { + Self::new(self.dir, -self.num) + } +} +impl HasUnits for PrimPitches { + /// Every so often we need the raw number, fine. Use sparingly. + #[inline(always)] + fn raw(&self) -> Int { + self.num + } +} +/// Numeric operations between primitive-pitch values. +/// Generally panic if operating on two [PrimPitches] with different directions. +impl std::ops::Add for PrimPitches { + type Output = PrimPitches; + fn add(self, rhs: Self) -> Self::Output { + if self.dir != rhs.dir { + panic!( + "Invalid attempt to add opposite-direction {:?} and {:?}", + self, rhs + ); + } + Self { + dir: self.dir, + num: self.num + rhs.num, + } + } +} +impl std::ops::AddAssign for PrimPitches { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} +impl std::ops::Sub for PrimPitches { + type Output = PrimPitches; + fn sub(self, rhs: Self) -> Self::Output { + if self.dir != rhs.dir { + panic!( + "Invalid attempt to add opposite-direction {:?} and {:?}", + self, rhs + ); + } + Self { + dir: self.dir, + num: self.num - rhs.num, + } + } +} +impl std::ops::SubAssign for PrimPitches { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} +/// Numeric operations between primitive-pitch values and regular numerics. +impl std::ops::Mul for PrimPitches { + type Output = Self; + fn mul(self, rhs: Int) -> Self::Output { + Self::new(self.dir, self.num * rhs) + } +} +impl std::ops::MulAssign for PrimPitches { + fn mul_assign(&mut self, rhs: Int) { + self.num = self.num * rhs; + } +} +impl std::ops::Mul for PrimPitches { + type Output = Self; + fn mul(self, rhs: usize) -> Self::Output { + Self::new(self.dir, self.num * Int::try_from(rhs).unwrap()) + } +} +impl std::ops::MulAssign for PrimPitches { + fn mul_assign(&mut self, rhs: usize) { + self.num = self.num * Int::try_from(rhs).unwrap(); + } +} + +/// A Scalar Value in Layer-Pitches +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub struct LayerPitches { + layer: usize, + num: Int, +} +impl LayerPitches { + /// Create a new [LayerPitches] on layer (index) `layer` + pub fn new(layer: usize, num: Int) -> Self { + Self { layer, num } + } + /// Consume self, returning the underlying [usize] layer-index and [Int] number. + pub fn into_inner(self) -> (usize, Int) { + (self.layer, self.num) + } +} +impl HasUnits for LayerPitches { + /// Every so often we need the raw number, fine. Use sparingly. + #[inline(always)] + fn raw(&self) -> Int { + self.num + } +} +/// Numeric operations between pitch-values and regular numerics. +impl std::ops::Mul for LayerPitches { + type Output = Self; + fn mul(self, rhs: Int) -> Self::Output { + Self::new(self.layer, self.num * rhs) + } +} +impl std::ops::MulAssign for LayerPitches { + fn mul_assign(&mut self, rhs: Int) { + self.num = self.num * rhs; + } +} +impl std::ops::Mul for LayerPitches { + type Output = Self; + fn mul(self, rhs: usize) -> Self::Output { + Self::new(self.layer, self.num * Int::try_from(rhs).unwrap()) + } +} +impl std::ops::MulAssign for LayerPitches { + fn mul_assign(&mut self, rhs: usize) { + self.num = self.num * Int::try_from(rhs).unwrap(); + } +} + +/// Paired "type" zero-data enum for [UnitSpeced] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum UnitType { + DbUnits, + PrimPitches, + LayerPitches, +} + +/// Common geometric pairing of (x,y) coordinates +/// Represents points, sizes, rectangles, and anything else that pairs `x` and `y` fields. +/// *Only* instantiable with [HasUnits] data. +#[derive( + Debug, + Clone, + Copy, + Serialize, + Deserialize, + PartialEq, + Eq, + From, + Add, + AddAssign, + Sub, + SubAssign, + MulAssign, + DivAssign, + Sum, +)] +/// X-Y Cartesian Pair +pub struct Xy { + pub x: T, + pub y: T, +} +impl Xy { + /// Create a new [Xy]. + pub fn new(x: T, y: T) -> Xy { + Self { x, y } + } + /// Get the dimension in direction `dir` + /// Also available via the [Index] trait. + pub fn dir(&self, dir: Dir) -> &T { + match dir { + Dir::Horiz => &self.x, + Dir::Vert => &self.y, + } + } +} +impl Xy { + /// Create a new [Xy] with transposed coordinates. + pub fn transpose(&self) -> Xy { + Self { + y: self.x.clone(), + x: self.y.clone(), + } + } +} +impl Xy { + /// Get a non-unit-spec'ed raw integer [Xy] + pub fn raw(&self) -> Xy { + Xy::new(self.x.raw(), self.y.raw()) + } +} +impl std::ops::Index for Xy { + type Output = T; + fn index(&self, dir: Dir) -> &Self::Output { + match dir { + Dir::Horiz => &self.x, + Dir::Vert => &self.y, + } + } +} +impl From<(Int, Int)> for Xy { + fn from(tup: (Int, Int)) -> Self { + Self { + x: tup.0.into(), + y: tup.1.into(), + } + } +} +impl From<(Int, Int)> for Xy { + fn from(tup: (Int, Int)) -> Self { + Self::new( + PrimPitches { + dir: Dir::Horiz, + num: tup.0.into(), + }, + PrimPitches { + dir: Dir::Vert, + num: tup.1.into(), + }, + ) + } +} diff --git a/Tetris/tetris/group.rs b/Tetris/tetris/group.rs new file mode 100644 index 0000000..6d56d8d --- /dev/null +++ b/Tetris/tetris/group.rs @@ -0,0 +1,51 @@ +//! +//! # Layout Element Groups +//! +//! The primary [Group] type is a set of named, located elements +//! which can be placed and moved together. +//! + +// Local imports +use crate::{ + array::Array, + cell::Cell, + coords::{PrimPitches, Xy}, + placement::Place, + raw::LayoutResult, + utils::Ptr, +}; + +/// Named group of placeable elements +#[derive(Debug, Clone)] +pub struct Group { + /// Group Name + name: String, + /// Constituent Elements + elements: Vec, +} +impl Group { + /// Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. + pub fn boundbox_size(&self) -> LayoutResult> { + todo!() + } +} +/// Enumeration of types that can be Grouped +#[derive(Debug, Clone)] +pub enum Groupable { + /// Instance of a Cell + Instance(Ptr), + /// Uniform array of placeable elements + Array(Ptr), + /// Group of other placeable elements + Group(Ptr), +} +/// Placed Instance of a [Group] +#[derive(Debug, Clone)] +pub struct GroupInstance { + /// Group-Instance Name + pub name: String, + /// Group Definition + pub group: Ptr, + /// Location + pub loc: Place>, +} diff --git a/Tetris/tetris/instance.rs b/Tetris/tetris/instance.rs new file mode 100644 index 0000000..6a26ca9 --- /dev/null +++ b/Tetris/tetris/instance.rs @@ -0,0 +1,77 @@ +//! +//! # Instance Structures +//! +//! Located, oriented instances of other cells or similar reusable layout objects. +//! + +// Local imports +use crate::bbox::{BoundBox, HasBoundBox}; +use crate::cell::Cell; +use crate::coords::{PrimPitches, Xy}; +use crate::placement::Place; +use crate::raw::{Dir, LayoutError, LayoutResult}; +use crate::utils::Ptr; + +/// Instance of another Cell +#[derive(Debug, Clone)] +pub struct Instance { + /// Instance Name + pub inst_name: String, + /// Cell Definition Reference + pub cell: Ptr, + /// Location of the Instance origin + /// This origin-position holds regardless of either `reflect` field. + /// If specified in absolute coordinates, location-units are [PrimPitches]. + pub loc: Place>, + /// Horizontal Reflection + pub reflect_horiz: bool, + /// Vertical Reflection + pub reflect_vert: bool, +} +impl Instance { + /// Boolean indication of whether this Instance is reflected in direction `dir` + pub fn reflected(&self, dir: Dir) -> bool { + match dir { + Dir::Horiz => self.reflect_horiz, + Dir::Vert => self.reflect_vert, + } + } + /// Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. + pub fn boundbox_size(&self) -> LayoutResult> { + let cell = self.cell.read()?; + cell.boundbox_size() + } +} +impl std::fmt::Display for Instance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let cell_name = { + let cell = self.cell.read().unwrap(); + cell.name.clone() + }; + write!( + f, + "Instance(name={}, cell={}, loc={:?})", + self.inst_name, cell_name, self.loc + ) + } +} +impl HasBoundBox for Instance { + type Units = PrimPitches; + type Error = LayoutError; + /// Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. + /// Instance location must be resolved to absolute coordinates, or this method will fail. + fn boundbox(&self) -> LayoutResult> { + let loc = self.loc.abs()?; + let cell = self.cell.read()?; + let outline = cell.outline()?; + let (x0, x1) = match self.reflect_horiz { + false => (loc.x, loc.x + outline.xmax()), + true => (loc.x - outline.xmax(), loc.x), + }; + let (y0, y1) = match self.reflect_vert { + false => (loc.y, loc.y + outline.ymax()), + true => (loc.y - outline.ymax(), loc.y), + }; + Ok(BoundBox::new(Xy::new(x0, y0), Xy::new(x1, y1))) + } +} diff --git a/Tetris/tetris/interface.rs b/Tetris/tetris/interface.rs new file mode 100644 index 0000000..83b5e50 --- /dev/null +++ b/Tetris/tetris/interface.rs @@ -0,0 +1,36 @@ +//! +//! # Interfaces Module +//! +//! Describing Cells in terms of their IO Interfaces +//! + +// Crates.io Imports +use serde::{Deserialize, Serialize}; + +/// # Port +/// +/// Logical port, as in a netlist or HDL description. +/// Includes scalar, vector (bus), and bundle-valued ports. +/// Does not include physical/ geometric information. +/// +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Port { + /// Port Name + pub name: String, + /// Port Type & Content + pub kind: PortKind, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PortKind { + /// Flat Scalar Port, e.g. `clk` + Scalar, + /// Array-Based Port, e.g. `data[31:0]` + Array { width: usize }, + /// Instance of a Hierarchical Bundle + Bundle { bundle_name: String }, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Bundle { + pub name: String, + pub ports: Vec, +} diff --git a/Tetris/tetris/layout.rs b/Tetris/tetris/layout.rs new file mode 100644 index 0000000..391c920 --- /dev/null +++ b/Tetris/tetris/layout.rs @@ -0,0 +1,104 @@ +//! +//! # Layout-Cell Definitions +//! +//! Physical implementations of tetris [Cell]s. +//! + +// Local imports +use crate::{ + instance::Instance, + outline, + placement::Placeable, + stack::{Assign, RelZ}, + tracks::TrackCross, + utils::PtrList, +}; + +/// # Layout Cell Implementation +/// +/// A combination of lower-level cell instances and net-assignments to tracks. +/// +#[derive(Debug, Clone, Builder)] +#[builder(pattern = "owned", setter(into))] +pub struct Layout { + /// Cell Name + pub name: String, + /// Number of Metal Layers Used + pub metals: usize, + /// Outline shape, counted in x and y pitches of `stack` + pub outline: outline::Outline, + + /// Layout Instances + #[builder(default)] + pub instances: PtrList, + /// Net-to-track assignments + #[builder(default)] + pub assignments: Vec, + /// Track cuts + #[builder(default)] + pub cuts: Vec, + /// Placeable objects + #[builder(default)] + pub places: Vec, +} +impl Layout { + /// Create a new [Layout] + pub fn new(name: impl Into, metals: usize, outline: outline::Outline) -> Self { + let name = name.into(); + Layout { + name, + metals, + outline, + instances: PtrList::new(), + assignments: Vec::new(), + cuts: Vec::new(), + places: Vec::new(), + } + } + /// Create a [LayoutBuilder], a struct created by the [Builder] macro. + pub fn builder() -> LayoutBuilder { + LayoutBuilder::default() + } + /// Assign a net at the given coordinates. + pub fn assign( + &mut self, + net: impl Into, + layer: usize, + track: usize, + at: usize, + relz: RelZ, + ) { + let net = net.into(); + let at = TrackCross::from_relz(layer, track, at, relz); + self.assignments.push(Assign { net, at }) + } + /// Add a cut at the specified coordinates. + pub fn cut(&mut self, layer: usize, track: usize, at: usize, relz: RelZ) { + let cut = TrackCross::from_relz(layer, track, at, relz); + self.cuts.push(cut) + } + /// Get a temporary handle for net assignments + pub fn net<'h>(&'h mut self, net: impl Into) -> NetHandle<'h> { + let name = net.into(); + NetHandle { name, parent: self } + } +} +/// # Net Handle +/// +/// A short-term handle for chaining multiple assignments to a net +/// Typically used as: `mycell.net("name").at(/* args */).at(/* more args */)` +/// Takes an exclusive reference to its parent [Layout], +/// so generally must be dropped quickly to avoid locking it up. +/// +pub struct NetHandle<'h> { + name: String, + parent: &'h mut Layout, +} +impl<'h> NetHandle<'h> { + /// Assign our net at the given coordinates. + /// Consumes and returns `self` to enable chaining. + pub fn at(self, layer: usize, track: usize, at: usize, relz: RelZ) -> Self { + self.parent.assign(&self.name, layer, track, at, relz); + self + } +} diff --git a/Tetris/tetris/lib.rs b/Tetris/tetris/lib.rs new file mode 100644 index 0000000..00532c3 --- /dev/null +++ b/Tetris/tetris/lib.rs @@ -0,0 +1,35 @@ +//! +//! # Layout21 "Tetris" Semi-Custom Layout System +//! + +// External macro usages +#[macro_use] +extern crate derive_builder; + +// Modules +pub mod abs; +pub mod array; +pub mod bbox; +pub mod cell; +pub mod conv; +pub mod coords; +pub mod group; +pub mod instance; +pub mod interface; +pub mod layout; +pub mod library; +pub mod outline; +pub mod placement; +pub mod placer; +pub mod stack; +pub mod tracks; +pub mod validate; + +// Re-exports +pub use layout21protos as protos; +pub use layout21raw as raw; +pub use layout21utils as utils; + +/// Unit Tests Module +#[cfg(test)] +mod tests; diff --git a/Tetris/tetris/library.rs b/Tetris/tetris/library.rs new file mode 100644 index 0000000..3496c9a --- /dev/null +++ b/Tetris/tetris/library.rs @@ -0,0 +1,92 @@ +//! +//! # Layout Library Module +//! + +// Std-lib +use std::collections::HashSet; + +// Local imports +use crate::raw::LayoutResult; +use crate::utils::{Ptr, PtrList}; +use crate::{cell, conv, raw, validate}; + +/// # Layout Library +/// +/// A combination of cell definitions, sub-libraries, and metadata +/// +#[derive(Debug, Clone, Default)] +pub struct Library { + /// Library Name + pub name: String, + /// Cell Definitions + pub cells: PtrList, + /// [raw::Library] Definitions + pub rawlibs: PtrList, +} +impl Library { + /// Create a new and initially empty [Library] + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + ..Default::default() + } + } + /// Export to a [raw::Library] + pub fn to_raw(self, stack: validate::ValidStack) -> LayoutResult> { + conv::raw::RawExporter::convert(self, stack) + } + /// Add a [Cell] + pub fn add_cell(&mut self, cell: cell::Cell) -> Ptr { + self.cells.insert(cell) + } + /// Add a [raw::Library] + pub fn add_rawlib(&mut self, rawlib: raw::Library) -> Ptr { + self.rawlibs.insert(rawlib) + } + /// Create an ordered list in which dependent cells follow their dependencies. + pub fn dep_order(&self) -> Vec> { + DepOrder::order(self) + } +} + +/// # Dependency-Orderer +/// +/// Creates an ordered list in which dependent cells follow their dependencies. +/// FIXME: migrate to utils::DepOrder +/// +#[derive(Debug)] +pub struct DepOrder<'lib> { + lib: &'lib Library, + stack: Vec>, + seen: HashSet>, +} +impl<'lib> DepOrder<'lib> { + fn order(lib: &'lib Library) -> Vec> { + let mut myself = Self { + lib, + stack: Vec::new(), + seen: HashSet::new(), + }; + for cell in myself.lib.cells.iter() { + myself.push(cell); + } + myself.stack + } + fn push(&mut self, ptr: &Ptr) { + // If the Cell hasn't already been visited, depth-first search it + if !self.seen.contains(&ptr) { + // Read the cell-pointer + let cell = ptr.read().unwrap(); + // If the cell has an implementation, visit its [Instance]s before inserting it + if let Some(layout) = &cell.layout { + for ptr in layout.instances.iter() { + let inst = ptr.read().unwrap(); + self.push(&inst.cell); + } + } + // And insert the cell (pointer) itself + self.seen.insert(Ptr::clone(ptr)); + self.stack.push(Ptr::clone(ptr)); + } + } +} diff --git a/Tetris/tetris/outline.rs b/Tetris/tetris/outline.rs new file mode 100644 index 0000000..d562aa6 --- /dev/null +++ b/Tetris/tetris/outline.rs @@ -0,0 +1,99 @@ +//! +//! # Tetris-Cell Outlines +//! + +// Std-lib imports +use std::fmt::Debug; + +// Crates.io +use serde::{Deserialize, Serialize}; + +// Local imports +use crate::coords::{Int, PrimPitches}; +use crate::raw::{Dir, LayoutError, LayoutResult}; + +/// # Block Outline +/// +/// All block-outlines are "tetris shaped" rectilinear polygons, and are `layout21::tetris`'s namesake. +/// +/// These boundaries are closed, consist solely of 90-degree rectangular turns, +/// and are specified by a counter-clockwise set of points. +/// "Holes" such as the shapes "O" and "8" and "divots" such as the shapes "U" and "H" are not supported. +/// +/// Two equal-length vectors `x` and `y` describe an Outline's points. +/// Counter-clockwise-ness and divot-free-ness requires that: +/// * (a) `x` values are monotonically non-increasing, and +/// * (b) `y` values are monotonically non-decreasing +/// +/// Such an outline has vertices in Cartesian space at: +/// `[(0,0), (x[0], 0), (x[0], y[0]), (x[1], y[0]), ... , (0, y[-1]), (0,0)]` +/// With the first point at the origin, the final point at (0, y[-1]), and its connection back to the origin all implied. +/// +/// Example: a rectangular Outline would requires single entry for each of `x` and `y`, +/// at the rectangle's vertex opposite the origin in both axes. +/// +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Outline { + pub x: Vec, + pub y: Vec, +} +impl Outline { + /// Outline constructor, with inline checking for validity of `x` & `y` vectors + pub fn new(x: &[Int], y: &[Int]) -> LayoutResult { + // Convert into [PrimPitches] united-objects, and return a new Self. + let x = x.into_iter().map(|i| PrimPitches::x(*i)).collect(); + let y = y.into_iter().map(|i| PrimPitches::y(*i)).collect(); + Self::from_prim_pitches(x, y) + } + /// Outline constructor from primitive-pitches + pub fn from_prim_pitches(x: Vec, y: Vec) -> LayoutResult { + // Check that x and y are of compatible lengths + if x.len() < 1 || x.len() != y.len() { + LayoutError::fail("Invalid zero-length Outline dimensions")?; + } + // Check for: + // * Correct directions + // * all non-negative values + for k in 0..x.len() { + if x[k].dir != Dir::Horiz || y[k].dir != Dir::Vert { + LayoutError::fail("Invalid Outline direction(s)")?; + } + if x[k].num < 0 || y[k].num < 0 { + LayoutError::fail("Invalid Outline with negative coordinate(s)")?; + } + } + // Check for: + // * x non-increasing-ness, + // * y for non-decreasing-ness + for k in 1..x.len() { + if x[k].num > x[k - 1].num { + LayoutError::fail("Invalid Outline with non-increasing x-coordinates")?; + } + if y[k].num < y[k - 1].num { + LayoutError::fail("Invalid Outline with non-decreasing y-coordinates")?; + } + } + Ok(Self { x, y }) + } + /// Create a new rectangular outline of dimenions `x` by `y` + pub fn rect(x: Int, y: Int) -> LayoutResult { + Self::new(&[x], &[y]) + } + /// Maximum x-coordinate + /// (Which is also always the *first* x-coordinate) + pub fn xmax(&self) -> PrimPitches { + self.x[0] + } + /// Maximum y-coordinate + /// (Which is also always the *last* y-coordinate) + pub fn ymax(&self) -> PrimPitches { + self.y[self.y.len() - 1] + } + /// Maximum coordinate in [Dir] `dir` + pub fn max(&self, dir: Dir) -> PrimPitches { + match dir { + Dir::Horiz => self.xmax(), + Dir::Vert => self.ymax(), + } + } +} diff --git a/Tetris/tetris/placement.rs b/Tetris/tetris/placement.rs new file mode 100644 index 0000000..f793d26 --- /dev/null +++ b/Tetris/tetris/placement.rs @@ -0,0 +1,212 @@ +//! +//! # Layout21 Placement Module +//! + +// Local imports +use crate::{ + array::ArrayInstance, + cell::Cell, + coords::{HasUnits, Int, PrimPitches, UnitSpeced, Xy}, + group::GroupInstance, + instance::Instance, + raw::{Dir, LayoutError, LayoutResult}, + utils::Ptr, +}; + +/// # Placement Enumeration +/// +/// Includes absolute and relative placements. +/// +/// Absolute placements are in `Self::AbsType` units. +/// Relative placements use the [RelativePlace] struct, +/// which can be specified relative to any other [Placeable] object. +/// +#[derive(Debug, Clone)] +pub enum Place { + /// Absolute + Abs(AbsType), + /// Relative + Rel(RelativePlace), +} +impl Place { + /// Assert that our place is absolute, and retrieve a shared reference to the inner [Xy] value. + pub fn abs(&self) -> LayoutResult<&T> { + match self { + Place::Abs(ref xy) => Ok(xy), + Place::Rel(_) => { + LayoutError::fail("Asserted absolute-placement on a relative-placement") + } + } + } + /// Assert that our place is absolute, and retrieve a mutable reference to the inner [Xy] value. + pub fn abs_mut(&mut self) -> LayoutResult<&mut T> { + match self { + Place::Abs(ref mut xy) => Ok(xy), + Place::Rel(_) => { + LayoutError::fail("Asserted absolute-placement on a relative-placement") + } + } + } +} +impl From> for Place> { + /// Convert [Xy] values into [Place::Abs] absolute places + fn from(xy: Xy) -> Self { + Self::Abs(xy) + } +} +impl From<(T, T)> for Place> { + /// Two-tuples of unit-specified numerics are converted to an [Xy] value. + fn from((x, y): (T, T)) -> Self { + Self::Abs(Xy::new(x, y)) + } +} +impl From<(Int, Int)> for Place> { + /// Two-tuples of integers are converted to an [Xy] value. + fn from(tup: (Int, Int)) -> Self { + Self::Abs(Xy::from(tup)) + } +} +impl From for Place { + fn from(rel: RelativePlace) -> Self { + Self::Rel(rel) + } +} + +/// # Relatively-Placed Assignment +/// FIXME: merge back in with absoutely-placed [Assign] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RelAssign { + pub net: String, + pub loc: RelativePlace, +} +/// # Relative Placement +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RelativePlace { + /// Placement is relative `to` this + pub to: Placeable, + /// Placed on this `side` of `to` + pub side: Side, + /// Aligned to this aspect of `to` + pub align: Align, + /// Separation between the placement and the `to` + pub sep: Separation, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Side { + Top, + Bottom, + Left, + Right, +} +impl Side { + /// Get the side rotated 90 degrees clockwise + pub fn cw_90(&self) -> Self { + match self { + Self::Top => Self::Right, + Self::Right => Self::Bottom, + Self::Bottom => Self::Left, + Self::Left => Self::Top, + } + } + /// Get the side rotated 90 degrees counter-clockwise + pub fn ccw_90(&self) -> Self { + match self { + Self::Top => Self::Left, + Self::Left => Self::Bottom, + Self::Bottom => Self::Right, + Self::Right => Self::Top, + } + } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Align { + /// Side-to-side alignment + Side(Side), + /// Center-aligned + Center, + /// Port-to-port alignment + Ports(String, String), +} + +/// Enumerated means of specifying relative-placement separation +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SepBy { + /// Separated by [UnitSpeced]-distance in x and y, and by layers in z + UnitSpeced(UnitSpeced), + /// Separated by the size of another Cell + SizeOf(Ptr), +} +/// Three-dimensional separation units +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Separation { + pub x: Option, + pub y: Option, + pub z: Option, +} +impl Separation { + pub fn new(x: Option, y: Option, z: Option) -> Self { + Self { x, y, z } + } + pub fn x(x: SepBy) -> Self { + Self { + x: Some(x), + ..Default::default() + } + } + pub fn y(y: SepBy) -> Self { + Self { + y: Some(y), + ..Default::default() + } + } + pub fn z(z: isize) -> Self { + Self { + z: Some(z), + ..Default::default() + } + } + /// Get the separation in direction `dir` + pub fn dir(&self, dir: Dir) -> &Option { + match dir { + Dir::Horiz => &self.x, + Dir::Vert => &self.y, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Placeable { + /// Instance of another cell + Instance(Ptr), + /// Uniform array of placeable elements + Array(Ptr), + /// Group of other placeable elements + Group(Ptr), + /// Instance port location + Port { inst: Ptr, port: String }, + /// Assignment + Assign(Ptr), +} +impl Placeable { + /// Get the location of the placeable + pub fn loc(&self) -> LayoutResult>> { + let loc = match self { + Placeable::Instance(ref p) => { + let p = p.read()?; + p.loc.clone() + } + Placeable::Array(ref p) => { + let p = p.read()?; + p.loc.clone() + } + Placeable::Group(ref p) => { + let p = p.read()?; + p.loc.clone() + } + Placeable::Port { .. } => unimplemented!(), + Placeable::Assign(_) => unimplemented!(), + }; + Ok(loc) + } +} diff --git a/Tetris/tetris/placer.rs b/Tetris/tetris/placer.rs new file mode 100644 index 0000000..2b6d7ba --- /dev/null +++ b/Tetris/tetris/placer.rs @@ -0,0 +1,1000 @@ +//! +//! # Tetris Placer +//! +//! Converts potentially relatively-placed attributes to absolute positions. +//! + +// Std-Lib Imports +use std::convert::TryFrom; + +// Local imports +use crate::bbox::HasBoundBox; +use crate::{instance::Instance, layout::Layout}; +use crate::coords::{LayerPitches, PrimPitches, UnitSpeced, Xy}; +use crate::library::Library; +use crate::array::{Array, ArrayInstance, Arrayable}; +use crate::placement::{ + Align, Place, Placeable, RelativePlace, SepBy, Side, +}; +use crate::raw::{Dir, LayoutError, LayoutResult}; +use crate::utils::{DepOrder, DepOrderer, ErrorContext, ErrorHelper, Ptr}; +use crate::validate::ValidStack; +use crate::{ + abs, stack, + tracks::{TrackCross, TrackRef}, +}; + +/// # Placer +/// Converts all potentially-relatively-placed attributes to absolute positions. +pub struct Placer { + lib: Library, + stack: ValidStack, + ctx: Vec, +} +impl Placer { + /// [Placer] public API entrypoint. + /// Modify and return [Library] `lib`, converting all [RelativePlace]s to absolute locations. + pub fn place(lib: Library, stack: ValidStack) -> LayoutResult<(Library, ValidStack)> { + let mut this = Self { + lib, + stack, + ctx: Vec::new(), + }; + this.place_lib()?; + Ok((this.lib, this.stack)) + } + /// Primary internal implementation method. Update placements for [Library] `self.lib`. + fn place_lib(&mut self) -> LayoutResult<()> { + self.ctx.push(ErrorContext::Library(self.lib.name.clone())); + // Iterate over all the library's cells, updating their instance-placements. + for cellptr in &self.lib.dep_order() { + let mut cell = cellptr.write()?; + self.ctx.push(ErrorContext::Cell(cell.name.clone())); + if let Some(ref mut layout) = cell.layout { + self.place_layout(layout)?; + } + self.ctx.pop(); + } + self.ctx.pop(); + Ok(()) + } + /// Update placements for [Layout] `layout` + fn place_layout(&mut self, layout: &mut Layout) -> LayoutResult<()> { + self.ctx.push(ErrorContext::Impl); + + // Move `instances` and `places` into one vector of [Placeable]s + let mut places: Vec = layout + .instances + .drain(..) + .map(|i| Placeable::Instance(i)) + .collect(); + places.extend(layout.places.drain(..)); + + // Iterate over `places` in dependency order, updating any relative-places to absolute. + let mut ordered = PlaceOrder::order(&places)?; + for place in ordered.drain(..) { + match place { + Placeable::Instance(ref inst_ptr) => { + let mut inst = inst_ptr.write()?; + if let Place::Rel(ref rel) = inst.loc { + // Convert to an absolute location + let abs = self.resolve_instance_place(&*inst, rel)?; + inst.loc = Place::Abs(abs); + } + // Add the now-absolute-placed inst to the `instances` list + layout.instances.push(inst_ptr.clone()); + } + Placeable::Array(ref ptr) => { + let mut array_inst = ptr.write()?; + if let Place::Rel(ref rel) = array_inst.loc { + // Convert to an absolute location + let abs = self.resolve_array_place(&*array_inst, rel)?; + array_inst.loc = Place::Abs(abs); + } + // And flatten its instances + let children = self.flatten_array_inst(&*array_inst)?; + let children = children.into_iter().map(|i| Ptr::new(i)); + layout.instances.extend(children); + } + Placeable::Assign(ref ptr) => { + let assn = ptr.read()?; + let abs: TrackCross = self.resolve_assign_place(&assn.loc)?; + let new_assn = stack::Assign { + net: assn.net.clone(), + at: abs, + }; + layout.assignments.push(new_assn); + } + Placeable::Group(_) => unimplemented!(), + Placeable::Port { .. } => (), // Nothing to do, at least until hitting something that *depends* on the Port location + } + } + self.ctx.pop(); + Ok(()) + } + /// Flatten an [ArrayInstance] to a vector of Cell Instances. + /// Instance location must be absolute by call-time. + fn flatten_array_inst( + &mut self, + array_inst: &ArrayInstance, + ) -> LayoutResult> { + // Read the child-Instances from the underlying [Array] definition + let mut children = { + let array = array_inst.array.read()?; + self.flatten_array(&*array, &array_inst.name)? + }; + // Get its initial location + let loc = array_inst.loc.abs()?; + // Translate each child to our location and reflection + for child in children.iter_mut() { + let childloc = child.loc.abs_mut()?; + if array_inst.reflect_horiz { + // Reflect horizontally + childloc.x *= -1_isize; + child.reflect_horiz = !child.reflect_horiz; + } + if array_inst.reflect_vert { + // Reflect vertically + childloc.y *= -1_isize; + child.reflect_vert = !child.reflect_vert; + } + // Translate its location + *childloc += *loc; + } + Ok(children) + } + /// Flatten an [Array] to a vector of Cell Instances. + fn flatten_array(&mut self, array: &Array, prefix: &str) -> LayoutResult> { + let mut insts = Vec::with_capacity(array.count); + + // Get the separations in each dimension + let xsep = match array.sep.x { + None => PrimPitches::x(0), + Some(SepBy::UnitSpeced(u)) => { + match u { + UnitSpeced::PrimPitches(p) => p.clone(), + _ => unimplemented!(), // TODO: other units + } + } + Some(SepBy::SizeOf(_)) => unimplemented!(), + }; + let ysep = match array.sep.y { + None => PrimPitches::y(0), + Some(SepBy::UnitSpeced(u)) => { + match u { + UnitSpeced::PrimPitches(p) => p.clone(), + _ => unimplemented!(), // TODO: other units + } + } + Some(SepBy::SizeOf(_)) => unimplemented!(), + }; + let sep = Xy::new(xsep, ysep); + + // Initialize our location to the array-origin + let mut loc = (0, 0).into(); + for i in 0..array.count { + match &array.unit { + Arrayable::Instance(cell) => { + let i = Instance { + inst_name: format!("{}[{}]", prefix, i), // `arrayname[i]` + cell: cell.clone(), + loc: Place::Abs(loc), + reflect_horiz: false, + reflect_vert: false, + }; + insts.push(i); + } + Arrayable::Array(arr) => { + // Create a new [ArrayInstance] at the current location, + // largely for sake of reusing `flatten_array_inst` + // to get its located, flattened children. + let i = ArrayInstance { + name: format!("{}[{}]", prefix, i), // `arrayname[i]` + array: arr.clone(), + loc: Place::Abs(loc), + reflect_horiz: false, + reflect_vert: false, + }; + // (Potentially recursively) flatten that short-lived [ArrayInstance] + let children = self.flatten_array_inst(&i)?; + // And add its children to ours + insts.extend(children); + } + Arrayable::Group(_arr) => unimplemented!(), + }; + // Increment the location by our (two-dimensional) increment. + loc += sep; + } + Ok(insts) + } + /// Resolve a location of [ArrayInstance] `inst` relative to its [RelativePlace] `rel`. + fn resolve_array_place( + &mut self, + _inst: &ArrayInstance, + _rel: &RelativePlace, + ) -> LayoutResult> { + // FIXME: this should just need the same stuff as `resolve_instance_place`, + // once we have a consolidated version of [Instance] that covers Arrays. + todo!() + } + /// Resolve a location of [Instance] `inst` relative to its [RelativePlace] `rel`. + fn resolve_instance_place( + &mut self, + inst: &Instance, + rel: &RelativePlace, + ) -> LayoutResult> { + self.ctx + .push(ErrorContext::Instance(inst.inst_name.clone())); + + // Get the relative-to instance's bounding box + let bbox = match rel.to { + Placeable::Instance(ref ptr) => ptr.read()?.boundbox()?, + Placeable::Array(ref ptr) => ptr.read()?.boundbox()?, + Placeable::Group(_) => unimplemented!(), + Placeable::Assign(_) => unimplemented!(), + Placeable::Port { .. } => unimplemented!(), + }; + // The coordinate axes here are referred to as `side`, corresponding to `rel.side`, and `align`, corresponding to `rel.align`. + // Mapping these back to (x,y) happens at the very end. + // FIXME: checks that `side` and `align` are orthogonal should come earlier + + // Get its edge-coordinates in each axis + let mut side_coord = bbox.side(rel.side); + let align_side = match rel.align { + Align::Side(s) => s, + _ => unimplemented!(), + }; + let mut align_coord = bbox.side(align_side); + let side_axis = match rel.side { + Side::Left | Side::Right => Dir::Horiz, + Side::Top | Side::Bottom => Dir::Vert, + }; + let align_axis = side_axis.other(); + // Sort out whether the instance needs a reflection-based offset in each axis + let offset_side = match rel.side { + Side::Left | Side::Bottom => !inst.reflected(side_axis), + Side::Top | Side::Right => inst.reflected(side_axis), + }; + let offset_align = match align_side { + Side::Left | Side::Bottom => inst.reflected(align_axis), + Side::Top | Side::Right => !inst.reflected(align_axis), + }; + // Add in any reflection-based offsets + if offset_side || offset_align { + let inst_size = inst.boundbox_size()?; + if offset_side { + if inst.reflected(side_axis) { + side_coord = side_coord + inst_size[side_axis]; + } else { + side_coord = side_coord - inst_size[side_axis]; + } + } + if offset_align { + if inst.reflected(align_axis) { + align_coord = align_coord + inst_size[align_axis]; + } else { + align_coord = align_coord - inst_size[align_axis]; + } + } + } + // Add in our separation + if rel.sep.z.is_some() { + self.fail("Z-axis separation is invalid for Instances")?; + } + if rel.sep.dir(align_axis).is_some() { + self.fail("Separation in the alignment-axis is invalid for Instances")?; + } + // Get the side-axis separation + let sep_side_axis = match &rel.sep.dir(side_axis) { + None => PrimPitches::new(side_axis, 0), + Some(SepBy::SizeOf(cellptr)) => { + let cell = cellptr.read()?; + cell.boundbox_size()?[side_axis] + } + Some(SepBy::UnitSpeced(ref u)) => { + match u { + UnitSpeced::DbUnits(_) => self.fail("Invalid separation units: DbUnits")?, + UnitSpeced::LayerPitches(_) => { + // Do a buncha coordinate transformations + todo!() + } + UnitSpeced::PrimPitches(ref p) => { + if p.dir != side_axis { + self.fail(format!("Separation {:?} specified in invalid axis", u))?; + } + p.clone() + } + } + } + }; + // Invert the separation if necessary + let sep_side_axis = match &rel.side { + Side::Top | Side::Right => sep_side_axis, + Side::Left | Side::Bottom => sep_side_axis.negate(), + }; + // And finally add it in + side_coord = side_coord + sep_side_axis; + // Move back to (x,y) coordinates + let res = match rel.side { + Side::Left | Side::Right => Xy::new(side_coord, align_coord), + Side::Top | Side::Bottom => Xy::new(align_coord, side_coord), + }; + self.ctx.pop(); + Ok(res) + } + /// Resolve the location of a track-crossing at `rel` + fn resolve_assign_place(&mut self, rel: &RelativePlace) -> LayoutResult { + let port_loc = match &rel.to { + Placeable::Port { inst, port } => self.locate_instance_port(&*inst.read()?, port)?, + Placeable::Instance(_) => unimplemented!(), + Placeable::Array(_) => unimplemented!(), + Placeable::Group(_) => unimplemented!(), + Placeable::Assign(_) => unimplemented!(), + }; + let ref_cross: (TrackRef, TrackRef) = match port_loc { + PortLoc::ZTopEdge { track, range } => { + let ortho_track = match rel.align { + Align::Side(s) => { + match s { + Side::Bottom | Side::Left => range.0.track, // FIXME: reflection support + Side::Top | Side::Right => range.1.track, // FIXME: reflection support + } + } + Align::Center => (range.0.track + range.1.track) / 2, + Align::Ports(_, _) => unreachable!(), + }; + ( + track.clone(), + TrackRef { + layer: range.0.layer, + track: ortho_track, + }, + ) + } + _ => unimplemented!(), + }; + + // Sort out separation (in tracks) in (x, y) + let _sep_x = match &rel.sep.x { + Some(_) => unimplemented!(), + None => 0_usize, + }; + let _sep_y = match &rel.sep.y { + Some(_) => unimplemented!(), + None => 0_usize, + }; + // Sort out the layer-based z-separation + let sep_z = match &rel.sep.z { + Some(i) => *i, + None => 0, + }; + let newlayer = isize::try_from(ref_cross.0.layer)? + sep_z; + let newlayer = usize::try_from(newlayer)?; + + // Of the two layers in `ref_cross`, one will be in parallel with `newlayer`, and one will be orthogonal to it. + // Set `par` as the parallel track, and `cross` as the orthogonal track. + let (par, cross) = { + let newlayer_dir = self.stack.metal(newlayer)?.spec.dir; + if newlayer_dir == self.stack.metal(ref_cross.0.layer)?.spec.dir { + (ref_cross.0, ref_cross.1) + } else if newlayer_dir == self.stack.metal(ref_cross.1.layer)?.spec.dir { + (ref_cross.1, ref_cross.0) + } else { + return self.fail("Invalid non-crossing TrackCross"); + } + }; + + // Get the track on `newlayer` closest to `par` + let new_track: TrackRef = self.convert_track_layer(&par, newlayer)?; + // And turn the combination into our result [TrackCross] + let rv = TrackCross::new(new_track, cross); + Ok(rv) + } + /// Resolve a location of [Instance] `inst` relative to its [RelativePlace] `rel`. + fn locate_instance_port(&mut self, inst: &Instance, portname: &str) -> LayoutResult { + // Port locations are only valid for Cells with Abs definitions. Otherwise fail. + let cell = inst.cell.read()?; // Note `cell` is alive for the duration of this function + let abs = self.unwrap( + cell.abs.as_ref(), + format!( + "Cannot Location Port {} on Cell {} with no Abs View", + portname, cell.name + ), + )?; + // Get the Port-object, or fail + let port = self.unwrap( + abs.port(portname), + format!("Cell {} has no Port {}", cell.name, portname), + )?; + + let loc = match &port.kind { + abs::PortKind::Edge { .. /*layer, track, side*/ } => unimplemented!(), + abs::PortKind::ZTopEdge { track, side, into } => { + let top_metal = self.unwrap(cell.top_metal()?, "No metal layers")?; + let (dir, nsignals) = { + // Get relevant data from our [Layer], and quickly drop a reference to it. + let layer = self.stack.metal(top_metal)?; + (layer.spec.dir, layer.period_data.signals.len()) + }; + let port_track = { + let layer_pitches = + self.layer_pitches(top_metal, inst.loc.abs()?[!dir].into())?; + + // Get the port's track-index, combining in the instance-location + let (_, period_tracks) = (layer_pitches * nsignals).into_inner(); + let period_tracks = usize::try_from(period_tracks)?; + if inst.reflected(!dir) { + period_tracks - track - 1 + } else { + period_tracks + track + } + }; + + // Sort out the orthogonal-axis range. + let ortho_layer = match into.1 { + stack::RelZ::Above => top_metal + 1, + stack::RelZ::Below => top_metal - 1, + }; + let ortho_range = { + let layer = &self.stack.metal(ortho_layer)?; + let nsignals = layer.period_data.signals.len(); + + // Find the origin and size in `ortho_layer` tracks + // FIXME: probably make this a method, and/or a track-index `HasUnits` + let loc = inst.loc.abs()?[dir]; + let loc = self.layer_pitches(ortho_layer, loc.into())?; + let (_, loc) = (loc * nsignals).into_inner(); + let loc = usize::try_from(loc)?; + let size = inst.boundbox_size()?[dir]; + let size = self.layer_pitches(ortho_layer, size.into())?; + let (_, size) = (size * nsignals).into_inner(); + let size = usize::try_from(size)?; + + // Pull out the number of tracks in the orthogonal-axis + let into = into.0; + // And sort out the orthogonal range, based on location, size, and `into` tracks + match (side, inst.reflected(dir)) { + (abs::Side::BottomOrLeft, false)=> (loc, loc+into), + (abs::Side::BottomOrLeft, true)=> (loc-into, loc), + (abs::Side::TopOrRight, false)=> (loc+into, loc+size), + (abs::Side::TopOrRight, true)=> (loc-size, loc-into), + } + }; + + // From all that, create a [PortLoc] + PortLoc::ZTopEdge { + track: TrackRef { + layer: top_metal, + track: port_track, + }, + range: ( + TrackRef { + layer: ortho_layer, + track: ortho_range.0, + }, + TrackRef { + layer: ortho_layer, + track: ortho_range.1 + }, + ), + } + } + abs::PortKind::ZTopInner { .. } => unimplemented!(), + }; + Ok(loc) + } + /// Convert a [TrackRef] to the closest track on another same-direction layer `to_layer`. + fn convert_track_layer( + &mut self, + trackref: &TrackRef, + to_layer: usize, + ) -> LayoutResult { + if trackref.layer == to_layer { + // Same layer, no conversion needed + return Ok(trackref.clone()); + } + let track_layer_dir = &self.stack.metal(trackref.layer)?.spec.dir; + let to_layer_dir = &self.stack.metal(to_layer)?.spec.dir; + if track_layer_dir != to_layer_dir { + // Orthogonal layers. Fail. + self.fail(format!( + "Cannot convert between tracks on {:?} layer {} and {:?} layer {}", + track_layer_dir, trackref.layer, to_layer_dir, to_layer + ))?; + } + // Normal case - actually do some work. + // First find the starting-track's center in [DbUnits] + let track_center = self.stack.metal(trackref.layer)?.center(trackref.track)?; + // And get the corresponding track-index on the other layer + let to_layer_index = self.stack.metal(to_layer)?.track_index(track_center)?; + Ok(TrackRef { + layer: to_layer, + track: to_layer_index, + }) + } + /// Convert a [UnitSpeced] distance `dist` into [LayerPitches] on layer `layer_index`. + /// Fails if `dist` is not an integer multiple of the pitch of `layer_index`. + fn layer_pitches( + &mut self, + layer_index: usize, + dist: UnitSpeced, + ) -> LayoutResult { + let layer = &self.stack.metal(layer_index)?; + let layer_pitch = layer.pitch; + let num = match dist { + UnitSpeced::DbUnits(_) => unimplemented!(), + UnitSpeced::LayerPitches(_) => unimplemented!(), + UnitSpeced::PrimPitches(p) => { + let dir = layer.spec.dir; + let prim_pitch = self.stack.prim.pitches[dir.other()]; + if layer_pitch % prim_pitch != 0 { + self.fail(format!( + "Invalid Conversion: Primitive (pitch={:?}) to Layer {} (pitch={:?})", + prim_pitch, layer_index, layer_pitch + ))?; + } + p.num * (layer_pitch / prim_pitch) + } + }; + Ok(LayerPitches::new(layer_index, num)) + } +} + +/// Resolved Locations corresponding to [abs::PortKind]s +#[derive(Debug, Clone)] +pub enum PortLoc { + Edge { + /// Port Track + track: TrackRef, + /// Crossing Track + at: TrackRef, + }, + ZTopEdge { + /// Port Track + track: TrackRef, + /// Extent on an adjacent layer + range: (TrackRef, TrackRef), + }, + ZTopInner { + /// Locations + locs: Vec, + }, +} +#[derive(Debug, Clone)] +pub struct Tbd; +impl ErrorHelper for Placer { + type Error = LayoutError; + fn err(&self, msg: impl Into) -> LayoutError { + LayoutError::Export { + message: msg.into(), + stack: self.ctx.clone(), + } + } +} + +/// Empty struct for implementing the [DepOrder] trait for relative-placed [Instance]s. +struct PlaceOrder; +impl DepOrder for PlaceOrder { + type Item = Placeable; + type Error = LayoutError; + + /// Process [Instance]-pointer `item` + fn process(item: &Placeable, orderer: &mut DepOrderer) -> LayoutResult<()> { + match item { + Placeable::Instance(ref p) => { + let inst = p.read()?; + if let Place::Rel(rel) = &inst.loc { + orderer.push(&rel.to)?; // Visit the dependency first + } + } + Placeable::Array(ref p) => { + if let Place::Rel(rel) = &p.read()?.loc { + orderer.push(&rel.to)?; // Visit the dependency first + } + } + Placeable::Group(ref p) => { + if let Place::Rel(rel) = &p.read()?.loc { + orderer.push(&rel.to)?; // Visit the dependency first + } + } + Placeable::Assign(a) => { + // Always relative, push it unconditionally + let a = a.read()?; + orderer.push(&a.loc.to)?; + } + Placeable::Port { ref inst, .. } => { + if let Place::Rel(rel) = &inst.read()?.loc { + orderer.push(&rel.to)?; // Visit the dependency first + } + } + }; + Ok(()) + } + fn fail() -> Result<(), Self::Error> { + Err(LayoutError::msg("Placement ordering error")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::outline::Outline; + use crate::cell::Cell; + use crate::placement::{Place, Placeable, RelAssign, RelativePlace, SepBy, Separation, Side}; + use crate::tests::{exports, stacks::SampleStacks}; + + #[test] + fn test_place1() -> LayoutResult<()> { + // Most basic smoke-test + Placer::place(Library::new("plib"), SampleStacks::empty()?)?; + Ok(()) + } + #[test] + fn test_place2() -> LayoutResult<()> { + // Initial test of relative placement + + let mut lib = Library::new("test_place2"); + // Create a unit cell which we'll instantiate a few times + let unit = Layout::new("unit", 0, Outline::rect(3, 7)?); + let unit = lib.cells.add(unit); + // Create an initial instance + let i0 = Instance { + inst_name: "i0".into(), + cell: unit.clone(), + loc: (47, 51).into(), + reflect_horiz: false, + reflect_vert: false, + }; + // Create the parent cell which instantiates it + let mut parent = Layout::new("parent", 0, Outline::rect(100, 100)?); + let i0 = parent.instances.add(i0); + // Create another Instance, placed relative to `i0` + let i1 = Instance { + inst_name: "i1".into(), + cell: unit.clone(), + loc: Place::Rel(RelativePlace { + to: Placeable::Instance(i0.clone()), + side: Side::Right, + align: Align::Side(Side::Bottom), + sep: Separation::default(), + }), + reflect_horiz: false, + reflect_vert: false, + }; + let i1 = parent.instances.add(i1); + let parent = lib.cells.add(parent); + + // The real code-under-test: run placement + let (lib, stack) = Placer::place(lib, SampleStacks::empty()?)?; + + // Checks on results + assert_eq!(lib.cells.len(), 2); + // Note these next two checks do *pointer* equality, not value equality + assert_eq!(lib.cells[0], unit); + assert_eq!(lib.cells[1], parent); + // Now check the locations + { + let inst = i0.read()?; + assert_eq!(*inst.loc.abs()?, Xy::::from((47, 51))); + } + { + let inst = i1.read()?; + assert_eq!(*inst.loc.abs()?, Xy::::from((50, 51))); + } + exports(lib, stack) + } + #[test] + fn test_place3() -> LayoutResult<()> { + // Test each relative side and alignment + + // Get the sample data + let SampleLib { + ibig, + // big, + lil, + mut lib, + mut parent, + .. + } = SampleLib::get()?; + lib.name = "test_place3".into(); + let relto = Placeable::Instance(ibig.clone()); + + // Relative-placement-adder closure + let mut add_inst = |inst_name: &str, side, align| { + let i = Instance { + inst_name: inst_name.into(), + cell: lil.clone(), + loc: Place::Rel(RelativePlace { + to: relto.clone(), + side, + align: Align::Side(align), + sep: Separation::default(), + }), + reflect_horiz: false, + reflect_vert: false, + }; + parent.instances.add(i) + }; + // Add a bunch of em + let i1 = add_inst("i1", Side::Left, Side::Bottom); + let i2 = add_inst("i2", Side::Right, Side::Bottom); + let i3 = add_inst("i3", Side::Bottom, Side::Left); + let i4 = add_inst("i4", Side::Bottom, Side::Right); + let i5 = add_inst("i5", Side::Left, Side::Top); + let i6 = add_inst("i6", Side::Right, Side::Top); + let i7 = add_inst("i7", Side::Top, Side::Left); + let i8 = add_inst("i8", Side::Top, Side::Right); + + // Add `parent` to the library + let _parent = lib.cells.add(parent); + + // The real code under test: run placement + let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; + + // And test the placed results + let bigbox = ibig.read()?.boundbox()?; + let ibox = i1.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left)); + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); + let ibox = i2.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right)); + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); + let ibox = i3.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom)); + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); + let ibox = i4.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom)); + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); + let ibox = i5.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left)); + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); + let ibox = i6.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right)); + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); + let ibox = i7.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top)); + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); + let ibox = i8.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top)); + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); + exports(lib, stack) + } + #[test] + fn test_place4() -> LayoutResult<()> { + // Test size-of separation + + // Get the sample data + let SampleLib { + ibig, + // big, + lil, + mut lib, + mut parent, + .. + } = SampleLib::get()?; + lib.name = "test_place4".into(); + + // Relative-placement-adder closure + let mut add_inst = |inst_name: &str, side, sep| { + let i = Instance { + inst_name: inst_name.into(), + cell: lil.clone(), + loc: Place::Rel(RelativePlace { + to: Placeable::Instance(ibig.clone()), + side, + align: Align::Side(side.cw_90()), // Leave out `align` as an arg, set one-turn CW of `side` + sep, + }), + reflect_horiz: false, + reflect_vert: false, + }; + parent.instances.add(i) + }; + // Add a bunch of em + let sep_x = Separation::x(SepBy::SizeOf(lil.clone())); + let i1 = add_inst("i1", Side::Left, sep_x.clone()); + let i2 = add_inst("i2", Side::Right, sep_x.clone()); + let sep_y = Separation::y(SepBy::SizeOf(lil.clone())); + let i3 = add_inst("i3", Side::Bottom, sep_y.clone()); + let i4 = add_inst("i4", Side::Top, sep_y.clone()); + // Add `parent` to the library + let _parent = lib.cells.add(parent); + + // The real code under test: run placement + let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; + + // And test the placed results + let lilsize = lil.read()?.boundbox_size()?; + let bigbox = ibig.read()?.boundbox()?; + let ibox = i1.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left) - lilsize.x); + let ibox = i2.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right) + lilsize.x); + let ibox = i3.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom) - lilsize.y); + let ibox = i4.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top) + lilsize.y); + + exports(lib, stack) + } + #[test] + fn test_place5() -> LayoutResult<()> { + // Test separation by units + + // Get the sample data + let SampleLib { + ibig, + // big, + lil, + mut lib, + mut parent, + .. + } = SampleLib::get()?; + lib.name = "test_place5".into(); + + // Relative-placement-adder closure + let mut add_inst = |inst_name: &str, side, sep| { + let i = Instance { + inst_name: inst_name.into(), + cell: lil.clone(), + loc: Place::Rel(RelativePlace { + to: Placeable::Instance(ibig.clone()), + side, + align: Align::Side(side.ccw_90()), // Leave out `align` as an arg, set one-turn CCW of `side` + sep, + }), + reflect_horiz: false, + reflect_vert: false, + }; + parent.instances.add(i) + }; + // Add a bunch of em + let dx = PrimPitches::new(Dir::Horiz, 1); + let sep_x = Separation::x(SepBy::UnitSpeced(dx.clone().into())); + let i1 = add_inst("i1", Side::Left, sep_x.clone()); + let i2 = add_inst("i2", Side::Right, sep_x.clone()); + let dy = PrimPitches::new(Dir::Vert, 5); + let sep_y = Separation::y(SepBy::UnitSpeced(dy.clone().into())); + let i3 = add_inst("i3", Side::Bottom, sep_y.clone()); + let i4 = add_inst("i4", Side::Top, sep_y.clone()); + // Add `parent` to the library + let _parent = lib.cells.add(parent); + + // The real code under test: run placement + let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; + + // And test the placed results + let bigbox = ibig.read()?.boundbox()?; + let ibox = i1.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left) - dx); + let ibox = i2.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right) + dx); + let ibox = i3.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); + assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom) - dy); + let ibox = i4.read()?.boundbox()?; + assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); + assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top) + dy); + + exports(lib, stack) + } + #[test] + fn test_place6() -> LayoutResult<()> { + // Test port-relative placement + + // Get the sample data + let SampleLib { + // ibig, + // big, + lil, + mut lib, + mut parent, + .. + } = SampleLib::get()?; + lib.name = "test_place6".into(); + + // Relative-placement-adder closure + let mut add_inst = |inst_name: &str| { + let i = Instance { + inst_name: inst_name.into(), + cell: lil.clone(), + loc: (0, 0).into(), + reflect_horiz: false, + reflect_vert: false, + }; + parent.instances.add(i) + }; + // Add a `lil` + let i1 = add_inst("i1"); + + // The "code under test": add a relative-placed `Assign`. + parent.places.push(Placeable::Assign(Ptr::new(RelAssign { + net: "NETPPP".into(), + loc: RelativePlace { + to: Placeable::Port { + inst: i1.clone(), + port: "PPP".into(), + }, + align: Align::Center, + side: Side::Left, // FIXME: kinda nonsense + sep: Separation::z(2), + }, + }))); + // Add `parent` to the library + let parent = lib.cells.add(parent); + + // The real code under test: run placement + let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; + + { + let p = parent.read()?; + let parent_layout = p.layout.as_ref().unwrap(); + assert_eq!(parent_layout.places.len(), 0); + assert_eq!(parent_layout.instances.len(), 2); + assert_eq!(parent_layout.cuts.len(), 0); + assert_eq!(parent_layout.assignments.len(), 1); + let assn = &parent_layout.assignments[0]; + assert_eq!(assn.net, "NETPPP"); + assert_eq!(assn.at.track.layer, 2); + assert_eq!(assn.at.track.track, 0); + assert_eq!(assn.at.cross.layer, 1); + assert_eq!(assn.at.cross.track, 1); + } + exports(lib, stack) + } + pub struct SampleLib { + pub lib: Library, + pub big: Ptr, + pub ibig: Ptr, + pub lil: Ptr, + pub parent: Layout, + } + impl SampleLib { + /// Get a sample library with test cells `big`, `lil`, and `parent`. + /// Designed for adding instances of `lil` relative to `big` all around `parent`. + pub fn get() -> LayoutResult { + let mut lib = Library::new("_rename_me_plz_"); + // Create a big center cell + let big = Layout::new("big", 1, Outline::rect(11, 12)?); + let big = lib.cells.add(big); + // Create the parent cell which instantiates it + let mut parent = Layout::new("parent", 3, Outline::rect(40, 35)?); + // Create an initial instance + let ibig = Instance { + inst_name: "ibig".into(), + cell: big.clone(), + loc: (16, 15).into(), + reflect_horiz: false, + reflect_vert: false, + }; + let ibig = parent.instances.add(ibig); + // Create a unit cell which we'll instantiate a few times around `ibig` + let mut lil = Cell::new("lil"); + lil.layout = Some(Layout::new("lil", 1, Outline::rect(2, 1)?)); + let mut lil_abs = abs::Abstract::new("lil", 1, Outline::rect(2, 1)?); + lil_abs.ports.push(abs::Port { + name: "PPP".into(), + kind: abs::PortKind::ZTopEdge { + track: 0, + side: abs::Side::BottomOrLeft, + into: (2, stack::RelZ::Above), + }, + }); + lil.abs = Some(lil_abs); + let lil = lib.cells.add(lil); + Ok(SampleLib { + lib, + big, + ibig, + lil, + parent, + }) + } + } +} diff --git a/Tetris/tetris/stack.rs b/Tetris/tetris/stack.rs new file mode 100644 index 0000000..b7baf54 --- /dev/null +++ b/Tetris/tetris/stack.rs @@ -0,0 +1,348 @@ +// Std-lib imports +use std::fmt::Debug; + +// Crates.io +use serde::{Deserialize, Serialize}; + +// Local imports +use crate::coords::{DbUnits, Xy}; +use crate::instance::Instance; +use crate::raw::{self, Dir, LayoutResult, Units}; +use crate::utils::Ptr; +use crate::{tracks::*, validate}; + +/// # Stack +/// +/// The z-stack, primarily including metal, via, and primitive layers +#[derive(Debug, Clone)] +pub struct Stack { + /// Measurement units + pub units: Units, + /// Primitive Layer + pub prim: PrimitiveLayer, + /// Set of metal layers + pub metals: Vec, + /// Set of via layers + pub vias: Vec, + /// [raw::Layer] Mappings + pub rawlayers: Option>, + /// Layer used for cell outlines/ boundaries + pub boundary_layer: Option, +} +impl Stack { + /// Run validation, consuming `self` and creating a [validate::ValidStack] + pub fn validate(self) -> LayoutResult { + validate::validate_stack(self) + } +} +/// # MetalLayer +/// +/// Metal layer in a [Stack] +/// Each layer is effectively infinite-spanning in one dimension, and periodic in the other. +/// Layers with `dir=Dir::Horiz` extend to infinity in x, and repeat in y, and vice-versa. +/// +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MetalLayer { + /// Layer Name + pub name: String, + /// Direction Enumeration (Horizontal/ Vertical) + pub dir: Dir, + /// Default size of wire-cuts + pub cutsize: DbUnits, + /// Track Size & Type Entries + pub entries: Vec, + /// Offset, in our periodic dimension + pub offset: DbUnits, + /// Overlap between periods + pub overlap: DbUnits, + /// Setting for period-by-period flipping + pub flip: FlipMode, + /// Primitive-layer relationship + pub prim: PrimitiveMode, + /// [raw::Layer] for exports + pub raw: Option, +} +#[derive(Debug, Clone, Default)] +pub struct LayerPeriodData { + pub signals: Vec, + pub rails: Vec, +} +impl MetalLayer { + /// Convert this [Layer]'s track-info into a [LayerPeriodData] + pub(crate) fn to_layer_period_data(&self) -> LayoutResult { + let mut period = LayerPeriodData::default(); + let mut cursor = self.offset; + for e in &self.entries() { + let d = e.width; + match e.ttype { + TrackType::Gap => (), + TrackType::Rail(_railkind) => { + period.rails.push(TrackData { + ttype: e.ttype, + index: period.rails.len(), + dir: self.dir, + start: cursor, + width: d, + }); + } + TrackType::Signal => { + period.signals.push(TrackData { + ttype: e.ttype, + index: period.signals.len(), + dir: self.dir, + start: cursor, + width: d, + }); + } + }; + cursor += d; + } + Ok(period) + } + /// Convert this [Layer]'s track-info into a [LayerPeriod] + pub(crate) fn to_layer_period<'me, 'lib>( + &'me self, + index: usize, + stop: impl Into, + ) -> LayoutResult> { + let stop = stop.into(); + let mut period = LayerPeriod::default(); + period.index = index; + let mut cursor = self.offset + (self.pitch() * index); + let entries = self.entries(); + let iterator: Box> = + if self.flip == FlipMode::EveryOther && index % 2 == 1 { + Box::new(entries.iter().rev()) + } else { + Box::new(entries.iter()) + }; + for e in iterator { + let d = e.width; + match e.ttype { + TrackType::Gap => (), + TrackType::Rail(railkind) => { + period.rails.push( + Track { + data: TrackData { + ttype: e.ttype, + index: period.rails.len(), + dir: self.dir, + start: cursor, + width: d, + }, + segments: vec![TrackSegment { + tp: TrackSegmentType::Rail(railkind), + start: 0.into(), + stop, + }], + } + .validate()?, + ); + } + TrackType::Signal => { + period.signals.push( + Track { + data: TrackData { + ttype: e.ttype, + index: period.signals.len(), + dir: self.dir, + start: cursor, + width: d, + }, + segments: vec![TrackSegment { + tp: TrackSegmentType::Wire { src: None }, + start: 0.into(), + stop, + }], + } + .validate()?, + ); + } + }; + cursor += d; + } + Ok(period) + } + /// Flatten our [Entry]s into a vector + /// Removes any nested patterns + pub(crate) fn entries(&self) -> Vec { + let mut v: Vec = Vec::new(); + for e in self.entries.iter() { + match e { + TrackSpec::Entry(ee) => v.push(ee.clone()), + // FIXME: why doesn't this recursively call `entries`? Seems it could/should. + TrackSpec::Repeat(p) => { + for _i in 0..p.nrep { + for ee in p.entries.iter() { + v.push(ee.clone()); + } + } + } + } + } + v + } + /// Sum up this [Layer]'s pitch + pub(crate) fn pitch(&self) -> DbUnits { + self.entries().iter().map(|e| e.width).sum::() - self.overlap + } +} + +/// Transformed single period of [Track]s on a [Layer] +/// Splits track-info between signals and rails. +/// Stores each as a [Track] struct, which moves to a (start, width) size-format, +/// and includes a vector of track-segments for cutting and assigning nets. +#[derive(Debug, Clone, Default)] +pub struct LayerPeriod<'lib> { + pub index: usize, + pub signals: Vec>, + pub rails: Vec>, +} +impl<'lib> LayerPeriod<'lib> { + /// Shift the period by `dist` in its periodic direction + pub fn offset(&mut self, dist: DbUnits) -> LayoutResult<()> { + for t in self.rails.iter_mut() { + t.data.start += dist; + } + for t in self.signals.iter_mut() { + t.data.start += dist; + } + Ok(()) + } + /// Set the stop position for all [Track]s to `stop` + pub fn stop(&mut self, stop: DbUnits) -> LayoutResult<()> { + for t in self.rails.iter_mut() { + t.stop(stop)?; + } + for t in self.signals.iter_mut() { + t.stop(stop)?; + } + Ok(()) + } + /// Cut all [Track]s from `start` to `stop`, + pub fn cut( + &mut self, + start: DbUnits, + stop: DbUnits, + src: &'lib TrackCross, + ) -> TrackResult<()> { + for t in self.rails.iter_mut() { + t.cut(start, stop, src)?; + } + for t in self.signals.iter_mut() { + t.cut(start, stop, src)?; + } + Ok(()) + } + /// Block all [Track]s from `start` to `stop`, + pub fn block(&mut self, start: DbUnits, stop: DbUnits, src: &Ptr) -> TrackResult<()> { + for t in self.rails.iter_mut() { + t.block(start, stop, src)?; + } + for t in self.signals.iter_mut() { + t.block(start, stop, src)?; + } + Ok(()) + } +} +/// # Via / Insulator Layer Between Metals +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ViaLayer { + /// Layer name + pub name: String, + /// Top of the two layers connected by this layer + pub top: ViaTarget, + /// Bottom of the two layers connected by this layer + pub bot: ViaTarget, + /// Via size + pub size: Xy, + /// Stream-out layer numbers + pub raw: Option, +} +/// # Via Targets +/// +/// Enumerates the things vias can "go between". +/// Generally either a numbered metal layer, or the primitive base-layers. +/// +/// Values stored in the `Metal` variant are treated as indicies into `Stack.metals`, +/// i.e. `Metal(0)` is the first metal layer defined in the stack. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ViaTarget { + /// Connect to the Primitive layer + Primitive, + /// Connect to an indexed metal layer + Metal(usize), +} +impl From for ViaTarget { + fn from(i: usize) -> Self { + Self::Metal(i) + } +} +impl From> for ViaTarget { + fn from(i: Option) -> Self { + match i { + None => Self::Primitive, + Some(i) => Self::Metal(i), + } + } +} +/// Assignment of a net onto a track-intersection +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Assign { + /// Net Name + pub net: String, + /// Track Intersection Location + pub at: TrackCross, +} +impl Assign { + /// Create a new [Assign] + pub fn new(net: impl Into, at: impl Into) -> Self { + Self { + net: net.into(), + at: at.into(), + } + } +} +/// Relative Z-Axis Reference to one Layer `Above` or `Below` another +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum RelZ { + Above, + Below, +} +impl RelZ { + pub fn other(&self) -> Self { + match *self { + RelZ::Above => RelZ::Below, + RelZ::Below => RelZ::Above, + } + } +} + +/// Indication of whether a layer flips in its periodic axis with every period, +/// as most standard-cell-style logic gates do. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum FlipMode { + EveryOther, + None, +} +/// Indication of whether a layer is owned by, partially included in, or external to the primitive blocks +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum PrimitiveMode { + /// Owned by Primitives + Prim, + /// Partially split between Primitives and Stack + Split, + /// Owned by the Stack + Stack, +} +/// Description of the primitive-level cells in a [Stack] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PrimitiveLayer { + pub pitches: Xy, +} +impl PrimitiveLayer { + /// Create a new [PrimitiveLayer] with the given pitches + pub fn new(pitches: Xy) -> Self { + Self { pitches } + } +} diff --git a/Tetris/tetris/tests/demos.rs b/Tetris/tetris/tests/demos.rs new file mode 100644 index 0000000..ee1fb15 --- /dev/null +++ b/Tetris/tetris/tests/demos.rs @@ -0,0 +1,21 @@ +// Local imports +use super::{exports, stacks::SampleStacks}; +use crate::{ + conv::proto::ProtoLibImporter, protos, raw::LayoutResult, utils::SerializationFormat::Yaml, +}; + +#[test] +fn empty() -> LayoutResult<()> { + behind_curtain(include_str!("empty.yaml")) +} + +#[test] +fn insts() -> LayoutResult<()> { + behind_curtain(include_str!("insts.yaml")) +} + +fn behind_curtain(yaml: &str) -> LayoutResult<()> { + let plib: protos::tetris::Library = Yaml.from_str(yaml)?; + let lib = ProtoLibImporter::import(&plib)?; + exports(lib, SampleStacks::pdka()?) +} diff --git a/Tetris/tetris/tests/empty.yaml b/Tetris/tetris/tests/empty.yaml new file mode 100644 index 0000000..b99215e --- /dev/null +++ b/Tetris/tetris/tests/empty.yaml @@ -0,0 +1,13 @@ +--- +domain: demolib +cells: + - name: democell + layout: + name: democell + outline: + x: [ 100 ] + y: [ 10 ] + metals: 0 + instances: [] + assignments: [] + cuts: [] \ No newline at end of file diff --git a/Tetris/tetris/tests/insts.yaml b/Tetris/tetris/tests/insts.yaml new file mode 100644 index 0000000..2a0e769 --- /dev/null +++ b/Tetris/tetris/tests/insts.yaml @@ -0,0 +1,32 @@ +--- +domain: demoinst +cells: + - name: unit + layout: + name: unit + outline: + x: [5] + y: [1] + metals: 2 + instances: [] + assignments: [] + cuts: [] + - name: democell + layout: + name: democell + outline: + x: [100] + y: [10] + metals: 5 + instances: + - name: unit1 + cell: + to: + Local: unit + loc: + place: + Abs: { x: 10, y: 1 } + reflect_horiz: false + reflect_vert: false + assignments: [] + cuts: [] diff --git a/Tetris/tetris/tests/mod.rs b/Tetris/tetris/tests/mod.rs new file mode 100644 index 0000000..7f53447 --- /dev/null +++ b/Tetris/tetris/tests/mod.rs @@ -0,0 +1,229 @@ +//! +//! # Unit Tests +//! + +// Local imports +use crate::{ + abs, cell::Cell, conv, instance::Instance, layout::Layout, library::Library, outline::Outline, + raw::LayoutResult, stack::*, tracks::*, utils::PtrList, validate::ValidStack, +}; + +// Modules +pub mod demos; +pub mod ro; +pub mod stacks; +use stacks::SampleStacks; + +/// Create an empty cell +#[test] +fn empty_cell() -> LayoutResult<()> { + let c = Layout { + name: "EmptyCell".into(), + metals: 5, + outline: Outline::rect(50, 5)?, + instances: PtrList::new(), + assignments: Vec::new(), + cuts: Vec::new(), + places: Vec::new(), + }; + let mut lib = Library::new("EmptyCellLib"); + let _c2 = lib.cells.insert(Cell::from(c)); + exports(lib, SampleStacks::pdka()?) +} +/// Create a layout-implementation +#[test] +fn create_layout() -> LayoutResult<()> { + Layout { + name: "HereGoes".into(), + metals: 4, + outline: Outline::rect(50, 5)?, + instances: PtrList::new(), + assignments: vec![Assign { + net: "clk".into(), + at: TrackCross::from_relz(1, 0, 1, RelZ::Above), + }], + cuts: Vec::new(), + places: Vec::new(), + }; + Ok(()) +} +/// Create a library +#[test] +fn create_lib1() -> LayoutResult<()> { + let mut lib = Library::new("lib1"); + + lib.cells.insert(Layout { + name: "HereGoes".into(), + metals: 3, + outline: Outline::rect(50, 5)?, + instances: PtrList::new(), + assignments: vec![Assign { + net: "clk".into(), + at: TrackCross::from_relz(1, 4, 2, RelZ::Below), + }], + cuts: vec![ + TrackCross::from_relz(0, 1, 1, RelZ::Above), + TrackCross::from_relz(0, 1, 3, RelZ::Above), + TrackCross::from_relz(0, 1, 5, RelZ::Above), + TrackCross::from_relz(1, 1, 1, RelZ::Below), + TrackCross::from_relz(1, 1, 3, RelZ::Below), + TrackCross::from_relz(1, 1, 5, RelZ::Below), + ], + places: Vec::new(), + }); + exports(lib, SampleStacks::pdka()?) +} +/// Create a cell with instances +#[test] +fn create_lib2() -> LayoutResult<()> { + let mut lib = Library::new("lib2"); + let c2 = Layout::new("IsInst", 2, Outline::rect(100, 10)?); + let c2 = lib.cells.insert(c2); + + lib.cells.insert(Layout { + name: "HasInst".into(), + metals: 4, + outline: Outline::rect(200, 20)?, + instances: vec![Instance { + inst_name: "inst1".into(), + cell: c2, + loc: (20, 2).into(), + reflect_horiz: false, + reflect_vert: false, + }] + .into(), + assignments: vec![Assign { + net: "clk".into(), + at: TrackCross::from_relz(1, 1, 1, RelZ::Above), + }], + cuts: Vec::new(), + places: Vec::new(), + }); + exports(lib, SampleStacks::pdka()?) +} + +/// Create an abstract layout, with its variety of supported port types +#[test] +fn create_abstract() -> LayoutResult<()> { + let outline = Outline::rect(11, 11)?; + let ports = vec![ + abs::Port { + name: "edge_bot".into(), + kind: abs::PortKind::Edge { + layer: 2, + track: 2, + side: abs::Side::BottomOrLeft, + }, + }, + abs::Port { + name: "edge_top".into(), + kind: abs::PortKind::Edge { + layer: 2, + track: 4, + side: abs::Side::TopOrRight, + }, + }, + abs::Port { + name: "edge_left".into(), + kind: abs::PortKind::Edge { + layer: 1, + track: 1, + side: abs::Side::BottomOrLeft, + }, + }, + abs::Port { + name: "edge_right".into(), + kind: abs::PortKind::Edge { + layer: 1, + track: 5, + side: abs::Side::TopOrRight, + }, + }, + ]; + abs::Abstract { + name: "abstrack".into(), + outline, + metals: 4, + ports, + }; + Ok(()) +} + +/// Create a cell with abstract instances +#[test] +fn create_lib3() -> LayoutResult<()> { + let mut lib = Library::new("lib3"); + + let c2 = lib.cells.insert(abs::Abstract { + name: "IsAbs".into(), + metals: 1, + outline: Outline::rect(100, 10)?, + ports: Vec::new(), + }); + + lib.cells.insert(Layout { + name: "HasAbss".into(), + metals: 4, + outline: Outline::rect(500, 50)?, + instances: vec![ + Instance { + inst_name: "inst1".into(), + cell: c2.clone(), + loc: (0, 0).into(), + reflect_horiz: false, + reflect_vert: false, + }, + Instance { + inst_name: "inst2".into(), + cell: c2.clone(), + loc: (200, 20).into(), + reflect_horiz: false, + reflect_vert: false, + }, + Instance { + inst_name: "inst4".into(), + cell: c2.clone(), + loc: (400, 40).into(), + reflect_horiz: false, + reflect_vert: false, + }, + ] + .into(), + assignments: Vec::new(), + cuts: Vec::new(), + places: Vec::new(), + }); + exports(lib, SampleStacks::pdka()?) +} +/// Helper function. Export [Library] `lib` in several formats. +pub fn exports(lib: Library, stack: ValidStack) -> LayoutResult<()> { + // Serializable formats will generally be written as YAML. + use crate::utils::SerializationFormat::Yaml; + + let rawlib = conv::raw::RawExporter::convert(lib, stack)?; + let rawlib = rawlib.read()?; + + // Export to ProtoBuf, save as YAML and binary + let protolib = rawlib.to_proto()?; + Yaml.save( + &protolib, + &resource(&format!("{}.proto.yaml", &protolib.domain)), + ) + .unwrap(); + crate::raw::proto::proto::save( + &protolib, + &resource(&format!("{}.proto.bin", &protolib.domain)), + ) + .unwrap(); + + // Export to GDSII + let gds = rawlib.to_gds()?; + Yaml.save(&gds, &resource(&format!("{}.gds.yaml", &gds.name))) + .unwrap(); + gds.save(&resource(&format!("{}.gds", &gds.name)))?; + Ok(()) +} +/// Grab the full path of resource-file `fname` +fn resource(rname: &str) -> String { + format!("{}/resources/{}", env!("CARGO_MANIFEST_DIR"), rname) +} diff --git a/Tetris/tetris/tests/ro.rs b/Tetris/tetris/tests/ro.rs new file mode 100644 index 0000000..3a51810 --- /dev/null +++ b/Tetris/tetris/tests/ro.rs @@ -0,0 +1,474 @@ +//! +//! # Tests of one of our very favorite circuits to place in this framework! +//! + +// Local imports +use crate::abs; +use crate::array::{Array, ArrayInstance, Arrayable}; +use crate::cell::{Cell, RawLayoutPtr}; +use crate::coords::{PrimPitches, Xy}; +use crate::library::Library; +use crate::outline::Outline; +use crate::placement::{Align, Place, RelAssign}; +use crate::raw::{self, LayoutResult}; +use crate::stack::RelZ; +use crate::utils::Ptr; +use crate::{instance::Instance, layout::Layout}; + +// Test-locals +use super::{exports, resource, stacks::SampleStacks}; + +/// Create an abs unit-cell +fn abstract_unit_cell(_lib: &mut Library) -> LayoutResult> { + Ok(Ptr::new(abstract_unit()?.into())) +} +/// Create an abs unit-cell +fn abstract_unit() -> LayoutResult { + let unitsize = (18, 1); + + let unit = abs::Abstract { + name: "Wrapper".into(), + metals: 1, + outline: Outline::rect(unitsize.0, unitsize.1)?, + ports: vec![ + abs::Port { + name: "en".into(), + kind: abs::PortKind::ZTopEdge { + track: 2, + side: abs::Side::BottomOrLeft, + into: (5, RelZ::Above), + }, + }, + abs::Port { + name: "inp".into(), + kind: abs::PortKind::ZTopEdge { + track: 3, + side: abs::Side::TopOrRight, + into: (11, RelZ::Above), + }, + }, + abs::Port { + name: "out".into(), + kind: abs::PortKind::ZTopEdge { + track: 5, + side: abs::Side::TopOrRight, + into: (11, RelZ::Above), + }, + }, + ], + }; + Ok(unit) +} +/// RO, absolute-placement edition +fn ro_abs(unit: Ptr) -> LayoutResult { + let unitsize = (18, 1); + + // Create an initially empty layout + let mut ro = Layout::new( + "RO", // name + 4, // metals + Outline::rect(130, 7)?, // outline + ); + let m2xpitch = 36; + let m2botcut = 5; + let m2topcut = 7 * m2botcut + 1; + + // For each column + for x in 0..3 { + let m2track = (m2xpitch * (x + 1) - 4) as usize; + let m2entrack = (x * m2xpitch) + m2xpitch / 2; + + // For each row + for y in 0..3 { + let loc = ((2 * x + 1) * unitsize.0, (2 * (y + 1)) * unitsize.1).into(); + let inst = Instance { + inst_name: format!("inst{}{}", x, y), + cell: unit.clone(), + loc, + reflect_horiz: false, + reflect_vert: true, + }; + ro.instances.add(inst); + + // Assign the input + let m1track = (y * 12 + 9) as usize; + let m3track = m1track + x as usize; + ro.net(format!("dly{}", x)) + .at(1, m2track, m1track, RelZ::Below) + .at(2, m3track, m2track, RelZ::Below); + if x != 0 { + // Cut M3 to the *right* of the input + ro.cut(2, m3track, m2track + 1, RelZ::Below); + } else { + // Cut M3 to the *left* of the input + ro.cut(2, m3track, m2track - 1, RelZ::Below); + } + // Assign the output + let m3track = m1track + ((x + 1) % 3) as usize; + let m1track = (y * 12 + 11) as usize; + ro.net(format!("dly{}", ((x + 1) % 3))) + .at(1, m2track + 2, m1track, RelZ::Below) + .at(2, m3track, m2track + 2, RelZ::Below); + if x != 2 { + // Cut M3 to the *left* of the output + ro.cut(2, m3track, m2track + 1, RelZ::Below); + } else { + // Cut M3 to the *right* of the output + ro.cut(2, m3track, m2track + 3, RelZ::Below); + } + + // Assign the enable + let m1track = (y * 12 + 8) as usize; + let m2track = (m2entrack + y) as usize; + ro.net(format!("en{}{}", x, y)) + .at(1, m2track, m1track, RelZ::Below); + ro.cut(1, m2track, m1track + 1, RelZ::Below); // Cut just above + } + + // Make top & bottom M2 cuts + ro.cut(1, m2track, m2botcut, RelZ::Below); + ro.cut(1, m2track, m2topcut, RelZ::Below); + ro.cut(1, m2track + 2, m2botcut, RelZ::Below); + ro.cut(1, m2track + 2, m2topcut, RelZ::Below); + } + Ok(ro.into()) +} +/// RO, relative-placement edition +fn ro_rel(unit: Ptr) -> LayoutResult { + use crate::placement::{Placeable, RelativePlace, SepBy, Separation, Side}; + let unitsize = (18, 1); + + // Create an initially empty layout + let mut ro = Layout::builder() + .name("RO") + .metals(4_usize) + .outline(Outline::rect(130, 7)?) + .build()?; + let m2xpitch = 36; + let m2botcut = 5; + let m2topcut = 7 * m2botcut + 1; + + // Next-location tracker + let mut next_loc: Place> = (unitsize.0, 2 * unitsize.1).into(); + + // For each column + for x in 0..3 { + let m2track = (m2xpitch * (x + 1) - 4) as usize; + let m2entrack = (x * m2xpitch) + m2xpitch / 2; + let mut bottom_inst: Option> = None; + + // For each row + for y in 0..3 { + let inst = Instance { + inst_name: format!("inst{}{}", x, y), + cell: unit.clone(), + loc: next_loc, + reflect_horiz: false, + reflect_vert: true, + }; + let inst = ro.instances.add(inst); + if y == 0 { + bottom_inst = Some(inst.clone()); + } + + // Assign an input M2, at the center of its pin + let assn = Placeable::Assign(Ptr::new(RelAssign { + net: format!("dly{}", x), + loc: RelativePlace { + to: Placeable::Port { + inst: inst.clone(), + port: "inp".into(), + }, + side: Side::Right, + align: Align::Center, + sep: Separation::z(1), + }, + })); + ro.places.push(assn); + + // Assign an output M2, at the right-edge of the instance + let assn = Placeable::Assign(Ptr::new(RelAssign { + net: format!("dly{}", ((x + 1) % 3)), + loc: RelativePlace { + to: Placeable::Port { + inst: inst.clone(), + port: "out".into(), + }, + side: Side::Right, + align: Align::Side(Side::Right), + sep: Separation::z(1), + }, + })); + ro.places.push(assn); + + if y == 2 { + // Top of a row. Place to the right of its bottom instance. + next_loc = RelativePlace { + to: Placeable::Instance(bottom_inst.clone().unwrap()), + side: Side::Right, + align: Align::Side(Side::Bottom), // Top or Bottom both work just as well here + sep: Separation::x(SepBy::SizeOf(unit.clone())), + } + .into(); + } else { + // Place above the most-recent instance. + next_loc = RelativePlace { + to: Placeable::Instance(inst.clone()), + side: Side::Top, + align: Align::Side(Side::Left), // Left or Right both work just as well here + sep: Separation::y(SepBy::SizeOf(unit.clone())), + } + .into(); + } + + // Assign the input + let m1track = (y * 12 + 9) as usize; + let m3track = m1track + x as usize; + ro.net(format!("dly{}", x)) + .at(2, m3track, m2track, RelZ::Below); + + if x != 0 { + // Cut M3 to the *right* of the input + ro.cut(2, m3track, m2track + 1, RelZ::Below); + } else { + // Cut M3 to the *left* of the input + ro.cut(2, m3track, m2track - 1, RelZ::Below); + } + // Assign the output + let m3track = m1track + ((x + 1) % 3) as usize; + + ro.net(format!("dly{}", ((x + 1) % 3))) + .at(2, m3track, m2track + 2, RelZ::Below); + + if x != 2 { + // Cut M3 to the *left* of the output + ro.cut(2, m3track, m2track + 1, RelZ::Below); + } else { + // Cut M3 to the *right* of the output + ro.cut(2, m3track, m2track + 3, RelZ::Below); + } + + // Assign the enable + let m1track = (y * 12 + 8) as usize; + let m2track = (m2entrack + y) as usize; + ro.net(format!("en{}{}", x, y)) + .at(1, m2track, m1track, RelZ::Below); + ro.cut(1, m2track, m1track + 1, RelZ::Below); // Cut just above + } + + // Make top & bottom M2 cuts + ro.cut(1, m2track, m2botcut, RelZ::Below); + ro.cut(1, m2track, m2topcut, RelZ::Below); + ro.cut(1, m2track + 2, m2botcut, RelZ::Below); + ro.cut(1, m2track + 2, m2topcut, RelZ::Below); + } + Ok(ro.into()) +} +/// Test importing and wrapping an existing GDSII into a [Library]/[Cell] +#[test] +fn wrap_gds() -> LayoutResult<()> { + let mut lib = Library::new("wrap_gds"); + _wrap_gds(&mut lib)?; + exports(lib, SampleStacks::pdka()?) +} +/// Most internal implementation of the `wrap_gds` test +fn _wrap_gds(lib: &mut Library) -> LayoutResult> { + // Import a [GdsLibrary] to a [raw::Library] + let gds_fname = resource("ginv.gds"); + let gds = raw::gds::gds21::GdsLibrary::load(&gds_fname)?; + + let stack = SampleStacks::pdka()?; + + let rawlib = raw::Library::from_gds(&gds, Some(Ptr::clone(&stack.rawlayers.unwrap())))?; + assert_eq!(rawlib.cells.len(), 1); + // Get a [Ptr] to the first (and only) cell + let cell = rawlib.cells.first().unwrap().clone(); + + // Add tracking of our dependence on the [raw::Library] + let rawlibptr = lib.add_rawlib(rawlib); + // Create a [Cell] from the [raw::Library]'s sole cell + let unitsize = (18, 1); + let wrapped = RawLayoutPtr { + outline: Outline::rect(unitsize.0, unitsize.1)?, // outline + metals: 1, + lib: rawlibptr, + cell, + }; + let wrapped = lib.cells.insert(wrapped); + + // Create a wrapper cell + let mut wrapper = Layout::new( + "Wrapper", // name + 1, // metals + Outline::rect(unitsize.0, unitsize.1)?, // outline + ); + wrapper.instances.add(Instance { + inst_name: "wrapped".into(), + cell: wrapped, + loc: (0, 0).into(), + reflect_horiz: false, + reflect_vert: false, + }); + // Convert the layout to a [Cell] + let mut wrapper: Cell = wrapper.into(); + // And add an [Abstract] view + wrapper.abs = Some(abstract_unit()?); + // Finally add the wrapper [Cell] to our [Library], and return a pointer to it. + let wrapper = lib.cells.insert(wrapper); + Ok(wrapper) +} +/// RO, array-placement edition +fn ro_array(unit: Ptr) -> LayoutResult { + use crate::placement::{Placeable, SepBy, Separation}; + let unitsize = (18, 1); + + // Create an initially empty layout + let mut ro = Layout::new( + "RO", // name + 4, // metals + Outline::rect(130, 7)?, // outline + ); + let m2xpitch = 36; + let m2botcut = 5; + let m2topcut = 7 * m2botcut + 1; + + // Create the main array of Instances + let a = Placeable::Array(Ptr::new(ArrayInstance { + name: "insts".into(), + loc: (unitsize.0, 6 * unitsize.1).into(), + reflect_vert: true, + reflect_horiz: false, + array: Ptr::new(Array { + name: "col".into(), + count: 3, + sep: Separation::y(SepBy::UnitSpeced(PrimPitches::y(2).into())), + unit: Arrayable::Array(Ptr::new(Array { + name: "row".into(), + unit: Arrayable::Instance(unit.clone()), + count: 3, + sep: Separation::x(SepBy::UnitSpeced(PrimPitches::x(36).into())), // FIXME! + })), + }), + })); + ro.places.push(a); + + // Now do all of the metal-layer stuff: assignments and cuts + // This part remains in absolute coordinates + + // For each column + for x in 0..3 { + let m2track = (m2xpitch * (x + 1) - 4) as usize; + let m2entrack = (x * m2xpitch) + m2xpitch / 2; + + // For each row + for y in 0..3 { + /* + # Remaining TODO here: + * Assignments, for input, output, and enable of each instance + * Cuts + let mut group = Group::new("???"); + let instptr = group.add(Instance { + inst_name: "inst".into(), + cell: unit.clone(), + loc: Place::origin(), + reflect_horiz: false, + reflect_vert: true, + }); + let a1 = group.add(AssignPlace { + net: format!("dly{}", x), + at: RelSomething( + inst, + "out", // ? + Separation::z(1), // One layer up + Align::Center, // Align on pin-center + ), + }); + AssignSomething { + net: format!("dly{}", x), + at: RelSomething( + a1, // Relative to the last assignment + Separation::z(2), // Now *two* layers up + ), + }; + */ + // Assign the input + let m1track = (y * 12 + 9) as usize; + let m3track = m1track + x as usize; + ro.net(format!("dly{}", x)) + .at(1, m2track, m1track, RelZ::Below) + .at(2, m3track, m2track, RelZ::Below); + if x != 0 { + // Cut M3 to the *right* of the input + ro.cut(2, m3track, m2track + 1, RelZ::Below); + } else { + // Cut M3 to the *left* of the input + ro.cut(2, m3track, m2track - 1, RelZ::Below); + } + // Assign the output + let m3track = m1track + ((x + 1) % 3) as usize; + let m1track = (y * 12 + 11) as usize; + ro.net(format!("dly{}", ((x + 1) % 3))) + .at(1, m2track + 2, m1track, RelZ::Below) + .at(2, m3track, m2track + 2, RelZ::Below); + if x != 2 { + // Cut M3 to the *left* of the output + ro.cut(2, m3track, m2track + 1, RelZ::Below); + } else { + // Cut M3 to the *right* of the output + ro.cut(2, m3track, m2track + 3, RelZ::Below); + } + + // Assign the enable + let m1track = (y * 12 + 8) as usize; + let m2track = (m2entrack + y) as usize; + ro.net(format!("en{}{}", x, y)) + .at(1, m2track, m1track, RelZ::Below); + ro.cut(1, m2track, m1track + 1, RelZ::Below); // Cut just above + } + + // Make top & bottom M2 cuts + ro.cut(1, m2track, m2botcut, RelZ::Below); + ro.cut(1, m2track, m2topcut, RelZ::Below); + ro.cut(1, m2track + 2, m2botcut, RelZ::Below); + ro.cut(1, m2track + 2, m2topcut, RelZ::Below); + } + Ok(ro.into()) +} +/// Runner for each of these RO tests. +/// Accepts function-arguments for the unit-cell and wrapper-cell factories. +fn _ro_test( + libname: &str, + unitfn: fn(&mut Library) -> LayoutResult>, + wrapfn: fn(Ptr) -> LayoutResult, +) -> LayoutResult<()> { + let mut lib = Library::new(libname); + let unit = unitfn(&mut lib)?; // Create the unit cell + let ro = wrapfn(unit)?; // Create the RO level + lib.cells.insert(ro); // And add it to the Library + exports(lib, SampleStacks::pdka()?) // And export everything to our handful of formats +} +// Execute a bunch of combinations, each as a separate test +#[test] +fn ro_wrap_gds_abs() -> LayoutResult<()> { + _ro_test("RoWrapGdsAbs", _wrap_gds, ro_abs) +} +#[test] +fn ro_wrap_gds_rel() -> LayoutResult<()> { + _ro_test("RoWrapGdsRel", _wrap_gds, ro_rel) +} +#[test] +fn ro_wrap_gds_array() -> LayoutResult<()> { + _ro_test("RoWrapGdsArray", _wrap_gds, ro_array) +} +#[test] +fn ro_abs_abs() -> LayoutResult<()> { + _ro_test("RoAbsAbs", abstract_unit_cell, ro_abs) +} +#[test] +fn ro_abs_rel() -> LayoutResult<()> { + _ro_test("RoAbsRel", abstract_unit_cell, ro_rel) +} +#[test] +fn ro_abs_array() -> LayoutResult<()> { + _ro_test("RoAbsArray", abstract_unit_cell, ro_rel) +} diff --git a/Tetris/tetris/tests/stacks.rs b/Tetris/tetris/tests/stacks.rs new file mode 100644 index 0000000..fdaae66 --- /dev/null +++ b/Tetris/tetris/tests/stacks.rs @@ -0,0 +1,187 @@ +//! +//! # Test Sample [Stack]s +//! + +// Local imports +use crate::raw::{self, Dir, LayoutResult, Units}; +use crate::stack::*; +use crate::tracks::*; +use crate::utils::Ptr; +use crate::validate::ValidStack; + +/// # Sample Stacks +/// Namespace for commonly re-used [Stack]s for testing. +pub struct SampleStacks; + +impl SampleStacks { + /// As nearly empty a [Stack] as possible, while being raw-exportable. + /// Includes: + /// * A `boundary_layer` + /// * [raw::Layers] containing solely that boundary layer + /// * No metals or via layers + /// Generally useful for placement activities, particularly among [Instace]s. + pub fn empty() -> LayoutResult { + let mut rawlayers = raw::Layers::default(); + let boundary_layer = Some(rawlayers.add(raw::Layer::from_pairs( + 0, + &[(0, raw::LayerPurpose::Outline)], + )?)); + let stack = Stack { + units: Units::default(), + boundary_layer, + prim: PrimitiveLayer::new((100, 100).into()), + metals: Vec::new(), // No metal layers + vias: Vec::new(), // No vias + rawlayers: Some(Ptr::new(rawlayers)), + }; + Ok(stack.validate()?) + } + + /// Real(istic) PDK [Stack] + pub fn pdka() -> LayoutResult { + let mut rawlayers = raw::Layers::default(); + // Shorthands for the common purpose-numbers + let metal_purps = [ + (255, raw::LayerPurpose::Obstruction), + (20, raw::LayerPurpose::Drawing), + (5, raw::LayerPurpose::Label), + (16, raw::LayerPurpose::Pin), + ]; + let via_purps = [ + (255, raw::LayerPurpose::Obstruction), + (44, raw::LayerPurpose::Drawing), + (5, raw::LayerPurpose::Label), + (16, raw::LayerPurpose::Pin), + ]; + // Add a few base-layers that we are used in imported/ primitive cells, but not in our stack + rawlayers.add(raw::Layer::new(64, "nwell").add_pairs(&metal_purps)?); + rawlayers.add(raw::Layer::new(67, "li1").add_pairs(&metal_purps)?); + // Create the test stack + let stack = Stack { + units: Units::Nano, + boundary_layer: Some(rawlayers.add(raw::Layer::from_pairs( + 236, + &[(0, raw::LayerPurpose::Outline)], + )?)), + prim: PrimitiveLayer { + pitches: (460, 2720).into(), + }, + metals: vec![ + MetalLayer { + name: "met1".into(), + entries: vec![ + TrackSpec::gnd(480), + TrackSpec::repeat(vec![TrackEntry::gap(200), TrackEntry::sig(140)], 6), + TrackSpec::gap(200), + TrackSpec::pwr(480), + ], + dir: Dir::Horiz, + offset: (-240).into(), + cutsize: (250).into(), + overlap: (480).into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(68, &metal_purps)?)), + flip: FlipMode::EveryOther, + prim: PrimitiveMode::Split, + }, + MetalLayer { + name: "met2".into(), + entries: vec![TrackSpec::sig(140), TrackSpec::gap(320)], + dir: Dir::Vert, + cutsize: (250).into(), + offset: (-70).into(), + overlap: (0).into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(69, &metal_purps)?)), + flip: FlipMode::None, + prim: PrimitiveMode::Stack, + }, + MetalLayer { + name: "met3".into(), + entries: vec![ + TrackSpec::gnd(480), + TrackSpec::repeat(vec![TrackEntry::gap(200), TrackEntry::sig(140)], 6), + TrackSpec::gap(200), + TrackSpec::pwr(480), + ], + dir: Dir::Horiz, + offset: (-240).into(), + cutsize: (250).into(), + overlap: (480).into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(70, &metal_purps)?)), + flip: FlipMode::EveryOther, + prim: PrimitiveMode::Stack, + }, + MetalLayer { + name: "met4".into(), + entries: vec![ + TrackSpec::gnd(510), + TrackSpec::repeat(vec![TrackEntry::gap(410), TrackEntry::sig(50)], 8), + TrackSpec::gap(410), + TrackSpec::pwr(510), + ], + dir: Dir::Vert, + cutsize: (250).into(), + offset: (-255).into(), + overlap: (510).into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(71, &metal_purps)?)), + flip: FlipMode::EveryOther, + prim: PrimitiveMode::Stack, + }, + MetalLayer { + name: "met5".into(), + entries: vec![ + TrackSpec::gnd(480), + TrackSpec::repeat(vec![TrackEntry::gap(200), TrackEntry::sig(140)], 6), + TrackSpec::gap(200), + TrackSpec::pwr(480), + ], + dir: Dir::Horiz, + offset: (-240).into(), + cutsize: (250).into(), + overlap: (480).into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(72, &metal_purps)?)), + flip: FlipMode::EveryOther, + prim: PrimitiveMode::Stack, + }, + ], + vias: vec![ + ViaLayer { + name: "mcon".into(), + size: (240, 240).into(), + bot: ViaTarget::Primitive, + top: ViaTarget::Metal(0), + raw: Some(rawlayers.add(raw::Layer::from_pairs(67, &via_purps)?)), + }, + ViaLayer { + name: "via1".into(), + size: (240, 240).into(), + bot: 0.into(), + top: 1.into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(68, &via_purps)?)), + }, + ViaLayer { + name: "via2".into(), + size: (240, 240).into(), + bot: 1.into(), + top: 2.into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(69, &via_purps)?)), + }, + ViaLayer { + name: "via3".into(), + size: (240, 240).into(), + bot: 2.into(), + top: 3.into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(70, &via_purps)?)), + }, + ViaLayer { + name: "via4".into(), + size: (240, 240).into(), + bot: 3.into(), + top: 4.into(), + raw: Some(rawlayers.add(raw::Layer::from_pairs(71, &via_purps)?)), + }, + ], + rawlayers: Some(Ptr::new(rawlayers)), + }; + Ok(stack.validate()?) + } +} diff --git a/Tetris/tetris/tracks.rs b/Tetris/tetris/tracks.rs new file mode 100644 index 0000000..172db9c --- /dev/null +++ b/Tetris/tetris/tracks.rs @@ -0,0 +1,407 @@ +// Std-lib imports +use std::fmt::Debug; + +// Crates.io +use serde::{Deserialize, Serialize}; + +// Local imports +use crate::coords::DbUnits; +use crate::instance::Instance; +use crate::raw::{Dir, LayoutError, LayoutResult}; +use crate::stack::{Assign, RelZ}; +use crate::utils::Ptr; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TrackEntry { + pub ttype: TrackType, + pub width: DbUnits, +} +impl TrackEntry { + /// Helper method: create of [TrackEntry] of [TrackType] [TrackType::Gap] + pub fn gap(width: impl Into) -> Self { + TrackEntry { + width: width.into(), + ttype: TrackType::Gap, + } + } + /// Helper method: create of [TrackEntry] of [TrackType] [TrackType::Signal] + pub fn sig(width: impl Into) -> Self { + TrackEntry { + width: width.into(), + ttype: TrackType::Signal, + } + } +} +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum TrackType { + Gap, + Signal, + Rail(RailKind), +} +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum RailKind { + Pwr, + Gnd, +} +impl RailKind { + pub fn to_string(&self) -> String { + match self { + Self::Pwr => "VDD".into(), + Self::Gnd => "VSS".into(), + } + } +} +/// # Track "Specification" Entry +/// +/// Either a single entry, or repitition thereof. +/// +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum TrackSpec { + Entry(TrackEntry), + Repeat(Repeat), +} +impl TrackSpec { + pub fn gap(width: impl Into) -> Self { + Self::Entry(TrackEntry { + width: width.into(), + ttype: TrackType::Gap, + }) + } + pub fn sig(width: impl Into) -> Self { + Self::Entry(TrackEntry { + width: width.into(), + ttype: TrackType::Signal, + }) + } + pub fn rail(width: impl Into, rk: RailKind) -> Self { + Self::Entry(TrackEntry { + width: width.into(), + ttype: TrackType::Rail(rk), + }) + } + pub fn pwr(width: impl Into) -> Self { + Self::Entry(TrackEntry { + width: width.into(), + ttype: TrackType::Rail(RailKind::Pwr), + }) + } + pub fn gnd(width: impl Into) -> Self { + Self::Entry(TrackEntry { + width: width.into(), + ttype: TrackType::Rail(RailKind::Gnd), + }) + } + pub fn repeat(e: impl Into>, nrep: usize) -> Self { + Self::Repeat(Repeat::new(e, nrep)) + } +} +/// An array of layout `Entries`, repeated `nrep` times +#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct Repeat { + pub entries: Vec, + pub nrep: usize, +} +impl Repeat { + pub fn new(e: impl Into>, nrep: usize) -> Self { + Self { + entries: e.into(), + nrep, + } + } +} +#[derive(Debug, Clone)] +pub struct TrackData { + /// Track Type (Rail, Signal) + pub ttype: TrackType, + /// Track Index + pub index: usize, + /// Direction + pub dir: Dir, + /// Starting-point in off-dir axis + pub start: DbUnits, + /// Track width + pub width: DbUnits, +} +/// # Track +/// +/// An "instantiated" track, including: +/// * Track-long data in a [TrackData], and +/// * A set of [TrackSegment]s +#[derive(Debug, Clone)] +pub struct Track<'lib> { + /// Track-long data + pub data: TrackData, + /// Set of wire-segments, in positional order + pub segments: Vec>, +} +impl<'lib> Track<'lib> { + /// Verify a (generally just-created) [Track] is valid + pub fn validate(self) -> LayoutResult { + if self.data.width < DbUnits(0) { + return Err(LayoutError::from("Negative Track Width")); + } + Ok(self) + } + /// Set the net of the track-segment at `at` to `net` + pub fn set_net(&mut self, at: DbUnits, assn: &'lib Assign) -> TrackResult<()> { + // First find the segment to be modified + let mut seg = None; + for s in self.segments.iter_mut() { + if s.start > at { + break; + } + if s.start <= at && s.stop >= at { + seg = Some(s); + break; + } + } + match seg { + None => Err(TrackError::OutOfBounds(at)), + Some(seg) => match seg.tp { + TrackSegmentType::Rail(_) => unreachable!(), + TrackSegmentType::Cut { .. } => Err(TrackError::Conflict( + // Error: trying to assign a net onto a Cut. + TrackConflict::Assign(assn.clone()), + TrackConflict::from(seg.tp.clone()), + )), + TrackSegmentType::Blockage { .. } => { + // FIXME: sort out the desired behaviour here. + // Vias above ZTop instance-pins generally land in this case. + // We could check for their locations? Or just let it go. + Ok(()) + } + TrackSegmentType::Wire { ref mut src, .. } => { + // The good case - assignment succeeds. + src.replace(assn); + Ok(()) + } + }, + } + } + /// Insert a cut or blockage corresponding to `blockage`. + pub fn cut_or_block( + &mut self, + start: DbUnits, + stop: DbUnits, + tp: TrackSegmentType<'lib>, + ) -> TrackResult<()> { + // First bounds-check against the end of our segments, which are the end of the cell + if stop > self.segments.last().unwrap().stop { + return Err(TrackError::OutOfBounds(stop)); + } + // Find the segment where the blockage starts + let segidx = self + .segments + .iter_mut() + .position(|seg| seg.stop > start) + .ok_or(TrackError::OutOfBounds(start))? + .clone(); + let seg = &mut self.segments[segidx]; + // Check for conflicts, and get a copy of our segment-type as we will likely insert a similar segment + let tpcopy = match seg.tp { + TrackSegmentType::Blockage { ref src } => { + return Err(TrackError::BlockageConflict( + TrackConflict::from(tp), + src.clone(), + )); + } + TrackSegmentType::Cut { src } => { + return Err(TrackError::CutConflict( + TrackConflict::from(tp), + src.clone(), + )); + } + TrackSegmentType::Wire { .. } => seg.tp.clone(), + TrackSegmentType::Rail(_) => seg.tp.clone(), + }; + // Make sure the cut only effects one segment, or fail + if seg.stop < stop { + // FIXME this should really be the *next* segment, borrow checking fight + return Err(TrackError::Overlap(seg.stop, stop)); + } + + // All clear; time to cut it. + // In the more-common case in which the cut-end and segment-end *do not* coincide, create and insert a new segment. + let mut to_be_inserted: Vec<(usize, TrackSegment)> = Vec::new(); + to_be_inserted.push((segidx + 1, TrackSegment { start, stop, tp })); + if seg.stop != stop { + let newseg = TrackSegment { + tp: tpcopy, + start: stop, + stop: seg.stop, + }; + to_be_inserted.push((segidx + 2, newseg)); + } + // Update the existing segment (and importantly, drop its mutable borrow) + seg.stop = start; + for (idx, seg) in to_be_inserted { + self.segments.insert(idx, seg); + } + Ok(()) + } + /// Insert a blockage from `start` to `stop`. + /// Fails if the region is not a contiguous wire segment. + pub fn block(&mut self, start: DbUnits, stop: DbUnits, src: &Ptr) -> TrackResult<()> { + self.cut_or_block(start, stop, TrackSegmentType::Blockage { src: src.clone() }) + } + /// Cut from `start` to `stop`. + /// Fails if the region is not a contiguous wire segment. + pub fn cut( + &mut self, + start: DbUnits, + stop: DbUnits, + src: &'lib TrackCross, + ) -> TrackResult<()> { + self.cut_or_block(start, stop, TrackSegmentType::Cut { src }) + } + /// Set the stop position for our last [TrackSegment] to `stop` + pub fn stop(&mut self, stop: DbUnits) -> LayoutResult<()> { + if self.segments.len() == 0 { + LayoutError::fail("Error Stopping Track")?; + } + let idx = self.segments.len() - 1; + self.segments[idx].stop = stop; + Ok(()) + } +} +/// # Segments of un-split, single-net wire on a [Track] +#[derive(Debug, Clone)] +pub struct TrackSegment<'lib> { + /// Segment-Type + pub tp: TrackSegmentType<'lib>, + /// Start Location, in [Stack]'s `units` + pub start: DbUnits, + /// End/Stop Location, in [Stack]'s `units` + pub stop: DbUnits, +} +#[derive(Debug, Clone)] +pub enum TrackSegmentType<'lib> { + Cut { src: &'lib TrackCross }, + Blockage { src: Ptr }, + Wire { src: Option<&'lib Assign> }, + Rail(RailKind), +} +/// # Track Reference +/// +/// Integer-pair representing a pointer to a [Layer] and track-index. +/// +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct TrackRef { + /// Layer Index + pub layer: usize, + /// Track Index + pub track: usize, +} +impl TrackRef { + /// Create a new [TrackRef] + pub fn new(layer: usize, track: usize) -> Self { + Self { layer, track } + } +} +/// # Track Crossing +/// +/// Located intersection between opposite-direction [Layer]s in [Track]-Space +/// +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct TrackCross { + /// "Primary" [Track] being referred to + pub track: TrackRef, + /// Intersecting "secondary" track + pub cross: TrackRef, +} +impl TrackCross { + pub fn new(track: TrackRef, cross: TrackRef) -> Self { + Self { track, cross } + } + /// Create from four [usize], representing the two (layer-index, track-index) pairs. + pub fn from_parts(layer1: usize, index1: usize, layer2: usize, index2: usize) -> Self { + Self { + track: TrackRef::new(layer1, index1), + cross: TrackRef::new(layer2, index2), + } + } + /// Create from a (layer-index, track-index) pair and a [RelZ] + pub fn from_relz(layer: usize, track: usize, at: usize, relz: RelZ) -> Self { + let layer2 = match relz { + RelZ::Above => layer + 1, + RelZ::Below => layer - 1, + }; + let track = TrackRef { layer, track }; + let cross = TrackRef { + layer: layer2, + track: at, + }; + Self::new(track, cross) + } +} + +#[derive(Debug, Clone)] +pub enum TrackConflict { + Assign(Assign), + Cut(TrackCross), + Blockage(Ptr), +} +impl std::fmt::Display for TrackConflict { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + // Delegate simpler types to [Debug] + TrackConflict::Assign(a) => std::fmt::Debug::fmt(a, f), + TrackConflict::Cut(c) => std::fmt::Debug::fmt(c, f), + // And for more complicated ones, [Display] + TrackConflict::Blockage(i) => std::fmt::Debug::fmt(i, f), + } + } +} +impl From> for TrackConflict { + fn from(tp: TrackSegmentType<'_>) -> Self { + match tp { + TrackSegmentType::Cut { src } => TrackConflict::Cut(src.clone()), + TrackSegmentType::Blockage { src } => TrackConflict::Blockage(src.clone()), + _ => unreachable!(), + } + } +} +pub enum TrackError { + OutOfBounds(DbUnits), + Overlap(DbUnits, DbUnits), + Conflict(TrackConflict, TrackConflict), + CutConflict(TrackConflict, TrackCross), + BlockageConflict(TrackConflict, Ptr), +} +pub type TrackResult = Result; +impl std::fmt::Debug for TrackError { + /// Display a [TrackError] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + TrackError::OutOfBounds(stop) => write!(f, "Track Out of Bounds: {:?}", stop), + TrackError::Overlap(p0, p1) => { + write!(f, "Overlapping Track cuts at: {:?}, {:?}", p0, p1) + } + TrackError::CutConflict(t0, t1) => { + write!(f, "Conflicting Track-Cuts at: {:?}, {:?}", t0, t1) + } + TrackError::BlockageConflict(t0, t1) => { + write!( + f, + "Conflicting Instance Blockages: \n * {}\n * {:?}\n", + t0, t1 + ) + } + TrackError::Conflict(t0, t1) => { + write!(f, "Conflict Between: \n * {}\n * {:?}\n", t0, t1) + } + } + } +} +impl std::fmt::Display for TrackError { + /// Display a [TrackError] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} +impl std::error::Error for TrackError {} +impl Into for TrackError { + fn into(self) -> LayoutError { + LayoutError::Boxed(Box::new(self)) + } +} diff --git a/Tetris/tetris/validate.rs b/Tetris/tetris/validate.rs new file mode 100644 index 0000000..7e77433 --- /dev/null +++ b/Tetris/tetris/validate.rs @@ -0,0 +1,375 @@ +//! +//! # Tetris Validators +//! +//! Integrity checks for [Stack]s, [Library]s, and the like. +//! + +// Std-Lib Imports +use std::convert::TryFrom; + +// Local imports +use crate::{ + abs::Abstract, + cell::Cell, + coords::{DbUnits, HasUnits}, + instance::Instance, + layout::Layout, + library::Library, + raw::{self, LayoutError, LayoutResult, Units}, + stack::{Assign, LayerPeriodData, MetalLayer, PrimitiveLayer, Stack}, + stack::{PrimitiveMode, ViaLayer, ViaTarget}, + tracks::{TrackCross, TrackRef}, + utils::{ErrorHelper, Ptr}, +}; + +/// Helper-function for asserting all sorts of boolean conditions, returning [LayoutResult] and enabling the question-mark operator. +pub fn assert(b: bool) -> LayoutResult<()> { + match b { + true => Ok(()), + false => LayoutError::fail("Assertion Failed"), + } +} +#[derive(Debug)] +pub struct StackValidator; +impl ErrorHelper for StackValidator { + type Error = LayoutError; + /// Errors are string-valued [LayoutError::String]s. + fn err(&self, msg: impl Into) -> Self::Error { + LayoutError::msg(msg) + } +} +/// Validate a [Stack], returning a [ValidStack] in its place. +pub fn validate_stack(stack: Stack) -> LayoutResult { + // Create a [StackValidator] instance, and use its internal instance method. + StackValidator.validate_stack(stack) +} +impl StackValidator { + /// Internal implementation of [validate_stack]. + fn validate_stack(&mut self, stack: Stack) -> LayoutResult { + let Stack { + units, + boundary_layer, + vias, + metals, + prim, + rawlayers, + .. + } = stack; + // Validate the primitive layer + self.assert( + prim.pitches.x.raw() > 0, + "Invalid zero or negative Primitive pitch", + )?; + self.assert( + prim.pitches.y.raw() > 0, + "Invalid zero or negative Primitive pitch", + )?; + + // Validate each metal layer + let mut valid_metals = Vec::new(); + for (num, layer) in metals.into_iter().enumerate() { + valid_metals.push(self.validate_metal(layer, num, &prim)?); + } + // Calculate pitches as the *least-common multiple* of same-direction layers below each layer + let mut pitches = vec![DbUnits(0); valid_metals.len()]; + for (num, metal) in valid_metals.iter().enumerate() { + let mut pitch = prim.pitches[!metal.spec.dir]; + for nn in 0..num + 1 { + if valid_metals[nn].spec.dir == metal.spec.dir { + pitch = num_integer::lcm(pitch.raw(), valid_metals[nn].pitch.raw()).into(); + } + } + pitches[num] = pitch; + } + // FIXME: add checks on [ViaLayer]s + // Stack checks out! Return its derived data + Ok(ValidStack { + units, + vias, + pitches, + metals: valid_metals, + prim, + rawlayers, + boundary_layer, + }) + } + /// Perform validation on a [Layer], return a corresponding [ValidMetalLayer] + pub fn validate_metal<'prim>( + &mut self, + layer: MetalLayer, + index: usize, + prim: &'prim PrimitiveLayer, + ) -> LayoutResult { + // Check for non-zero widths of all entries + for entry in layer.entries().iter() { + self.assert( + entry.width.raw() > 0, + format!( + "Invalid non-positive entry on {:?}: {:?}", + layer, entry.width + ), + )?; + } + let pitch = layer.pitch(); + self.assert( + pitch.raw() > 0, + format!( + "Invalid layer with non-positive pitch={}: {:?}", + pitch.raw(), + layer + ), + )?; + // Check for fit on the primitive grid, if the layer is in primitives + match layer.prim { + PrimitiveMode::Split | PrimitiveMode::Prim => { + let prim_pitch = prim.pitches[!layer.dir]; + self.assert(pitch % prim_pitch == 0, format!("Invalid layer {:?} shared with Primitives is not an integer multiple of the primitive pitch in the {:?} direction", layer, !layer.dir))?; + } + PrimitiveMode::Stack => (), + } + // Convert to a prototype [LayerPeriod] + // This is frequently used for calculating track locations + let period_data = layer.to_layer_period_data()?; + Ok(ValidMetalLayer { + raw: layer.raw.clone(), + spec: layer, + index, + period_data, + pitch, + }) + } +} + +/// Derived data for a [Stack], after it has gone through some validation steps. +#[derive(Debug)] +pub struct ValidStack { + /// Measurement units + pub units: Units, + /// Primitive layer + pub prim: PrimitiveLayer, + /// Set of via layers + pub vias: Vec, + /// Metal Layers + metals: Vec, + /// Pitches per metal layer, one each for those in `stack` + pub pitches: Vec, + + /// [raw::Layer] Mappings + pub rawlayers: Option>, + /// Layer used for cell outlines/ boundaries + pub boundary_layer: Option, +} +impl ValidStack { + /// Get Metal-Layer number `idx`. Returns `None` if `idx` is out of bounds. + pub fn metal(&self, idx: usize) -> LayoutResult<&ValidMetalLayer> { + if idx >= self.metals.len() { + LayoutError::fail(format!("Invalid metal index {}", idx)) + } else { + Ok(&self.metals[idx]) + } + } + /// Get the via-layer whose bottom "target" is metal-layer `idx`. + pub fn via_from(&self, idx: usize) -> LayoutResult<&ViaLayer> { + for via_layer in self.vias.iter() { + if let ViaTarget::Metal(k) = via_layer.bot { + if k == idx { + return Ok(via_layer); + } + } + } + LayoutError::fail(format!("Requiring undefined via from metal layer {}", idx)) + } + /// Get Via-Layer number `idx`. Returns an error if `idx` is out of bounds. + pub fn via(&self, idx: usize) -> LayoutResult<&ViaLayer> { + if idx >= self.vias.len() { + LayoutError::fail(format!("Invalid via index {}", idx)) + } else { + Ok(&self.vias[idx]) + } + } +} +#[derive(Debug)] +pub struct ValidMetalLayer { + /// Original Layer Spec + pub spec: MetalLayer, + + // Derived data + /// Index in layers array + pub index: usize, + /// Derived single-period template + pub period_data: LayerPeriodData, + /// Pitch in db-units + pub pitch: DbUnits, + /// Raw layer-key + pub raw: Option, +} +impl ValidMetalLayer { + /// Get the track-index at [DbUnits] `dist` + pub fn track_index(&self, dist: DbUnits) -> LayoutResult { + // FIXME: this, particularly the `position` call, grabs the first track that ends *after* `dist`. + // It could end up more helpful to do "closest" if `dist` is in-between two, + // or have some alignment options. + let npitches = dist / self.pitch; + let remainder = DbUnits(dist % self.pitch); + let mut index = usize::try_from(npitches)? * self.period_data.signals.len(); + + index += self + .period_data + .signals + .iter() + .position(|sig| sig.start + sig.width > remainder) + .unwrap(); + Ok(index) + } + /// Get the center-coordinate of signal-track `idx`, in our periodic dimension + pub fn center(&self, idx: usize) -> LayoutResult { + // FIXME: incorrect for asymmetric tracks via `FlipMode` turned on! + let len = self.period_data.signals.len(); + let track = &self.period_data.signals[idx % len]; + let mut cursor = self.pitch * (idx / len); + cursor += track.start + track.width / 2; + Ok(cursor) + } + /// Get the spanning-coordinates of signal-track `idx`, in our periodic dimension + pub fn span(&self, idx: usize) -> LayoutResult<(DbUnits, DbUnits)> { + let len = self.period_data.signals.len(); + let track = &self.period_data.signals[idx % len]; + let cursor = self.pitch * (idx / len) + track.start; + Ok((cursor, cursor + track.width)) + } +} +/// Validate [Library] `lib`. Requires a valid `stack`. +pub fn validate_lib(lib: &Library, stack: &ValidStack) -> LayoutResult<()> { + LibValidator::new(stack).validate_lib(lib) +} +/// # Library Validator +pub struct LibValidator<'stk> { + pub stack: &'stk ValidStack, +} +impl<'stk> LibValidator<'stk> { + pub(crate) fn new(stack: &'stk ValidStack) -> Self { + Self { stack } + } + pub(crate) fn validate_lib(&mut self, lib: &Library) -> LayoutResult<()> { + self.assert(lib.name.len() > 0, "Library name is empty")?; + for cellptr in lib.cells.iter() { + let mut cell = cellptr.write()?; + self.validate_cell(&mut *cell)?; + } + // FIXME: validate raw-content + Ok(()) + } + pub(crate) fn validate_cell(&mut self, cell: &mut Cell) -> LayoutResult<()> { + // FIXME: add checks on `metals`, `outline` + self.assert(cell.name.len() > 0, "Cell name is empty")?; + if let Some(ref mut abs) = cell.abs { + self.assert( + abs.name == cell.name, + format!( + "Cell name mismatch between Abstract {} and Cell {}", + abs.name, cell.name + ), + )?; + + self.validate_abstract(abs)?; + } + if let Some(ref mut layout) = cell.layout { + self.assert( + layout.name == cell.name, + format!( + "Cell name mismatch between Layout {} and Cell {}", + layout.name, cell.name + ), + )?; + self.validate_layout(layout)?; + } + // FIXME: validate any raw and circuit content + Ok(()) + } + pub(crate) fn validate_abstract(&mut self, _abs: &Abstract) -> LayoutResult<()> { + Ok(()) // FIXME! + } + pub(crate) fn validate_layout(&mut self, layout: &Layout) -> LayoutResult<()> { + for instptr in layout.instances.iter() { + let inst = instptr.read()?; + self.validate_instance(&*inst)?; + } + for cut in layout.cuts.iter() { + self.validate_track_cross(cut)?; + } + for assn in layout.assignments.iter() { + self.validate_assign(assn)?; + } + self.assert( + layout.places.len() == 0, + "Internal Error: Layout being validated without first being Placed ", + )?; + Ok(()) + } + pub(crate) fn validate_instance(&mut self, _inst: &Instance) -> LayoutResult<()> { + Ok(()) // FIXME! + } + pub(crate) fn validate_assign(&mut self, assn: &Assign) -> LayoutResult { + // Net "validation": just empty-string checking, at least for now + self.assert( + assn.net.len() > 0, + format!("Invalid zero-length net assigned at {:?}", assn.at), + )?; + // Validate the track-cross location + let i = &assn.at; + self.validate_track_cross(i)?; + // Arrange the two by top/bottom + let (top, bot) = if i.track.layer == i.cross.layer + 1 { + (i.track, i.cross) + } else if i.track.layer == i.cross.layer - 1 { + (i.cross, i.track) + } else { + return self.fail(format!("Invalid Assign on non-adjacent layers: {:?}", assn)); + }; + Ok(ValidAssign { + top, + bot, + src: assn.clone(), + }) + } + pub(crate) fn validate_track_cross(&mut self, i: &TrackCross) -> LayoutResult<()> { + // Validate both [TrackRef]s + self.validate_track_ref(&i.track)?; + self.validate_track_ref(&i.cross)?; + // Verify that the two are in opposite directions + if self.stack.metal(i.track.layer)?.spec.dir == self.stack.metal(i.cross.layer)?.spec.dir { + self.fail(format!( + "TrackCross {:?} and {:?} are in the same direction", + i.track, i.cross + ))?; + } + Ok(()) + } + pub(crate) fn validate_track_ref(&mut self, i: &TrackRef) -> LayoutResult<()> { + // Check that we won't reach outside the stack + self.assert( + i.layer < self.stack.metals.len(), + format!("Invalid TrackRef outside Stack: {:?}", i), + )?; + Ok(()) + } +} +impl ErrorHelper for LibValidator<'_> { + type Error = LayoutError; + /// Errors are string-valued [LayoutError::String]s. + fn err(&self, msg: impl Into) -> Self::Error { + LayoutError::msg(msg) + } +} + +/// # Validated Assignment +/// +/// Track-intersection including the invariant that `top` is one layer above `bot`, +/// such that the a via can be drawn between the two. +/// +#[derive(Debug, Clone)] +pub struct ValidAssign { + pub src: Assign, + pub top: TrackRef, + pub bot: TrackRef, +} From 58356ed4c26cd34f2a6038882f7a86b1684a79b7 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Wed, 11 May 2022 02:00:50 -0700 Subject: [PATCH 15/28] WIP Tetris Python porting --- Tetris/{tetris => }/tests/demos.rs | 0 Tetris/{tetris => }/tests/empty.yaml | 0 Tetris/{tetris => }/tests/insts.yaml | 0 Tetris/{tetris => }/tests/mod.rs | 0 Tetris/{tetris => }/tests/ro.rs | 0 Tetris/{tetris => }/tests/stacks.rs | 0 Tetris/tetris/__init__.py | 22 +- Tetris/tetris/cell.py | 118 ++++++ Tetris/tetris/cell.rs | 194 ---------- Tetris/tetris/coords.py | 265 +++++++++++++ Tetris/tetris/coords.rs | 359 ----------------- Tetris/tetris/error.py | 8 + Tetris/tetris/index.py | 10 + Tetris/tetris/instance.py | 66 ++++ Tetris/tetris/instance.rs | 77 ---- Tetris/tetris/{stack.rs => layer_period.py} | 291 +++----------- Tetris/tetris/layout.py | 58 +++ Tetris/tetris/layout.rs | 104 ----- Tetris/tetris/lib.rs | 35 -- Tetris/tetris/library.py | 74 ++++ Tetris/tetris/library.rs | 92 ----- Tetris/tetris/net_handle.py | 25 ++ Tetris/tetris/outline.py | 86 +++++ Tetris/tetris/outline.rs | 99 ----- Tetris/tetris/relz.py | 16 + Tetris/tetris/stack.py | 149 +++++++ Tetris/tetris/track_spec.py | 125 ++++++ Tetris/tetris/tracks.py | 253 ++++++++++++ Tetris/tetris/tracks.rs | 407 -------------------- 29 files changed, 1333 insertions(+), 1600 deletions(-) rename Tetris/{tetris => }/tests/demos.rs (100%) rename Tetris/{tetris => }/tests/empty.yaml (100%) rename Tetris/{tetris => }/tests/insts.yaml (100%) rename Tetris/{tetris => }/tests/mod.rs (100%) rename Tetris/{tetris => }/tests/ro.rs (100%) rename Tetris/{tetris => }/tests/stacks.rs (100%) create mode 100644 Tetris/tetris/cell.py delete mode 100644 Tetris/tetris/cell.rs create mode 100644 Tetris/tetris/coords.py delete mode 100644 Tetris/tetris/coords.rs create mode 100644 Tetris/tetris/error.py create mode 100644 Tetris/tetris/index.py create mode 100644 Tetris/tetris/instance.py delete mode 100644 Tetris/tetris/instance.rs rename Tetris/tetris/{stack.rs => layer_period.py} (52%) create mode 100644 Tetris/tetris/layout.py delete mode 100644 Tetris/tetris/layout.rs delete mode 100644 Tetris/tetris/lib.rs create mode 100644 Tetris/tetris/library.py delete mode 100644 Tetris/tetris/library.rs create mode 100644 Tetris/tetris/net_handle.py create mode 100644 Tetris/tetris/outline.py delete mode 100644 Tetris/tetris/outline.rs create mode 100644 Tetris/tetris/relz.py create mode 100644 Tetris/tetris/stack.py create mode 100644 Tetris/tetris/track_spec.py create mode 100644 Tetris/tetris/tracks.py delete mode 100644 Tetris/tetris/tracks.rs diff --git a/Tetris/tetris/tests/demos.rs b/Tetris/tests/demos.rs similarity index 100% rename from Tetris/tetris/tests/demos.rs rename to Tetris/tests/demos.rs diff --git a/Tetris/tetris/tests/empty.yaml b/Tetris/tests/empty.yaml similarity index 100% rename from Tetris/tetris/tests/empty.yaml rename to Tetris/tests/empty.yaml diff --git a/Tetris/tetris/tests/insts.yaml b/Tetris/tests/insts.yaml similarity index 100% rename from Tetris/tetris/tests/insts.yaml rename to Tetris/tests/insts.yaml diff --git a/Tetris/tetris/tests/mod.rs b/Tetris/tests/mod.rs similarity index 100% rename from Tetris/tetris/tests/mod.rs rename to Tetris/tests/mod.rs diff --git a/Tetris/tetris/tests/ro.rs b/Tetris/tests/ro.rs similarity index 100% rename from Tetris/tetris/tests/ro.rs rename to Tetris/tests/ro.rs diff --git a/Tetris/tetris/tests/stacks.rs b/Tetris/tests/stacks.rs similarity index 100% rename from Tetris/tetris/tests/stacks.rs rename to Tetris/tests/stacks.rs diff --git a/Tetris/tetris/__init__.py b/Tetris/tetris/__init__.py index d71ca68..f703a2d 100644 --- a/Tetris/tetris/__init__.py +++ b/Tetris/tetris/__init__.py @@ -1,5 +1,25 @@ - __version__ = "1.0.0.dev0" +from .coords import * +from .outline import * +from .relz import * +from .stack import * +from .track_spec import * + +from .layout import * +from .cell import * +from .library import * +# from .layer_period import * +# from .abs import * +# from .array import * +# from .bbox import * +# from .conv import * +# from .group import * +# from .instance import * +# from .interface import * +# from .placement import * +# from .placer import * +# from .tracks import * +# from .validate import * diff --git a/Tetris/tetris/cell.py b/Tetris/tetris/cell.py new file mode 100644 index 0000000..506ba30 --- /dev/null +++ b/Tetris/tetris/cell.py @@ -0,0 +1,118 @@ +# +# # Cell Definition +# +# Defines the [Cell] type, which represents a multi-viewed piece of reusable hardware. +# [Cell]s can, and generally do, have one or more associated "views", +# including [Abstract]s, [Layout], interface definitions, and/or "raw" layouts. +# + + +from typing import List, Set, Dict, Type, Union, Optional + +from pydantic.dataclasses import dataclass + +# Local imports +from .index import Index +from .coords import PrimPitches, Xy +from .layout import Layout +from .outline import Outline +from .error import LayoutError +# from .bundle import Bundle +# from .abstract import Abstract + +# "Pointer" to a raw (lib, cell) combination. +# Wraps with basic [Outline] and `metals` information to enable bounded placement. +@dataclass +class RawLayoutPtr: + ... # FIXME! handle `raw` stuff + + # # Outline shape, counted in x and y pitches of `stack` + # outline: Outline + # # Number of Metal Layers Used + # metals: int + # # Pointer to the raw Library + # lib: Ptr + # # Pointer to the raw Cell + # cell: Ptr + + +# # Cell View Enumeration +# All of the ways in which a Cell is represented +CellView = Union[ + "Bundle", "Abstract", Layout, RawLayoutPtr, +] + +# Collection of the Views describing a Cell +@dataclass +class Cell: + # Cell Name + name: str + # Interface + interface: Optional["Bundle"] = None + # Layout Abstract + abs: Optional["Abstract"] = None + # Layout Implementation + layout: Optional[Layout] = None + + # # Raw Layout + # # FIXME: this should probably move "up" a level + # # so that cells are either defined as `raw` or `tetris` implementations + # # but not both + # raw: Option + + # Add [CellView] `view` to our appropriate type-based field. + def add_view(self, view: CellView): + if isinstance(view, Layout): + self.layout = view + elif isinstance(view, Abstract): + self.abs = view + elif isinstance(view, Bundle): + self.interface = view + raise TypeError + + # Create from a list of [CellView]s and a name. + def from_views(name: str, views: List[CellView]) -> "Cell": + myself = Cell(name=name) + for view in views: + myself.add_view(view) + return myself + + # Return whichever view highest-prioritorily dictates the outline + def outline(self) -> Outline: + # We take the "most abstract" view for the outline + # (although if there are more than one, they better be the same... + # FIXME: this should be a validation step.) + # Overall this method probably should move to a "validated" cell in which each view is assured consistent. + if self.abs is not None: + return self.abs.outline + elif self.layout is not None: + return self.layout.outline + raise LayoutError( + "Failed to retrieve outline of cell : with no abstract or implementation", + ) + + # Size of the [Cell]'s rectangular `boundbox`. + def boundbox_size(self) -> Xy[PrimPitches]: + outline = self.outline() + return Xy.new(outline.xmax(), outline.ymax()) + + # Return whichever view highest-prioritorily dictates the top-layer + def metals(self) -> int: + # FIXME: same commentary as `outline` above + if self.abs is not None: + return self.abs.metals + elif self.layout is not None: + return self.layout.metals + raise LayoutError( + "Failed to retrieve metal-layers of cell : with no abstract or implementation", + ) + + # Get the cell's top metal layer (numer). + # Returns `None` if no metal layers are used. + def top_metal(self) -> Optional[Index]: + metals = self.metals() + if metals == 0: + return None + else: + return Index(metals - 1) + diff --git a/Tetris/tetris/cell.rs b/Tetris/tetris/cell.rs deleted file mode 100644 index d798581..0000000 --- a/Tetris/tetris/cell.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! -//! # Cell Definition -//! -//! Defines the [Cell] type, which represents a multi-viewed piece of reusable hardware. -//! [Cell]s can, and generally do, have one or more associated "views", -//! including [Abstract]s, [Layout], interface definitions, and/or "raw" layouts. -//! - -// Crates.io -use derive_more; - -// Local imports -use crate::coords::{PrimPitches, Xy}; -use crate::layout::Layout; -use crate::raw::{LayoutError, LayoutResult}; -use crate::utils::Ptr; -use crate::{abs, interface, outline, raw}; - -/// "Pointer" to a raw (lib, cell) combination. -/// Wraps with basic [Outline] and `metals` information to enable bounded placement. -#[derive(Debug, Clone)] -pub struct RawLayoutPtr { - /// Outline shape, counted in x and y pitches of `stack` - pub outline: outline::Outline, - /// Number of Metal Layers Used - pub metals: usize, - /// Pointer to the raw Library - pub lib: Ptr, - /// Pointer to the raw Cell - pub cell: Ptr, -} -/// # Cell View Enumeration -/// All of the ways in which a Cell is represented -#[derive(derive_more::From, Debug, Clone)] -pub enum CellView { - Interface(interface::Bundle), - Abstract(abs::Abstract), - Layout(Layout), - RawLayoutPtr(RawLayoutPtr), -} - -/// Collection of the Views describing a Cell -#[derive(Debug, Default, Clone)] -pub struct Cell { - /// Cell Name - pub name: String, - /// Interface - pub interface: Option, - /// Layout Abstract - pub abs: Option, - /// Layout Implementation - pub layout: Option, - /// Raw Layout - /// FIXME: this should probably move "up" a level, - /// so that cells are either defined as `raw` or `tetris` implementations, - /// but not both - pub raw: Option, -} -impl Cell { - /// Create a new and initially empty [Cell] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - ..Default::default() - } - } - /// Add [CellView] `view` to our appropriate type-based field. - pub fn add_view(&mut self, view: impl Into) { - let view = view.into(); - match view { - CellView::Interface(x) => { - self.interface.replace(x); - } - CellView::Abstract(x) => { - self.abs.replace(x); - } - CellView::Layout(x) => { - self.layout.replace(x); - } - CellView::RawLayoutPtr(x) => { - self.raw.replace(x); - } - } - } - /// Create from a list of [CellView]s and a name. - pub fn from_views(name: impl Into, views: Vec) -> Self { - let mut myself = Self::default(); - myself.name = name.into(); - for view in views { - myself.add_view(view); - } - myself - } - /// Return whichever view highest-prioritorily dictates the outline - pub fn outline(&self) -> LayoutResult<&outline::Outline> { - // We take the "most abstract" view for the outline - // (although if there are more than one, they better be the same... - // FIXME: this should be a validation step.) - // Overall this method probably should move to a "validated" cell in which each view is assured consistent. - if let Some(ref x) = self.abs { - Ok(&x.outline) - } else if let Some(ref x) = self.layout { - Ok(&x.outline) - } else if let Some(ref x) = self.raw { - Ok(&x.outline) - } else { - LayoutError::fail(format!( - "Failed to retrieve outline of cell {} with no abstract or implementation", - self.name, - )) - } - } - /// Size of the [Cell]'s rectangular `boundbox`. - pub fn boundbox_size(&self) -> LayoutResult> { - let outline = self.outline()?; - Ok(Xy::new(outline.xmax(), outline.ymax())) - } - /// Return whichever view highest-prioritorily dictates the top-layer - pub fn metals(&self) -> LayoutResult { - // FIXME: same commentary as `outline` above - if let Some(ref x) = self.abs { - Ok(x.metals) - } else if let Some(ref x) = self.layout { - Ok(x.metals) - } else if let Some(ref x) = self.raw { - Ok(x.metals) - } else { - LayoutError::fail(format!( - "Failed to retrieve metal-layers of cell {} with no abstract or implementation", - self.name, - )) - } - } - /// Get the cell's top metal layer (numer). - /// Returns `None` if no metal layers are used. - pub fn top_metal(&self) -> LayoutResult> { - let metals = self.metals()?; - if metals == 0 { - Ok(None) - } else { - Ok(Some(metals - 1)) - } - } -} -impl From for Cell { - fn from(src: CellView) -> Self { - match src { - CellView::Interface(x) => x.into(), - CellView::Abstract(x) => x.into(), - CellView::Layout(x) => x.into(), - CellView::RawLayoutPtr(x) => x.into(), - } - } -} -impl From for Cell { - fn from(src: interface::Bundle) -> Self { - Self { - name: src.name.clone(), - interface: Some(src), - ..Default::default() - } - } -} -impl From for Cell { - fn from(src: abs::Abstract) -> Self { - Self { - name: src.name.clone(), - abs: Some(src), - ..Default::default() - } - } -} -impl From for Cell { - fn from(src: Layout) -> Self { - Self { - name: src.name.clone(), - layout: Some(src), - ..Default::default() - } - } -} -impl From for Cell { - fn from(src: RawLayoutPtr) -> Self { - let name = { - let cell = src.cell.read().unwrap(); - cell.name.clone() - }; - Self { - name, - raw: Some(src), - ..Default::default() - } - } -} diff --git a/Tetris/tetris/coords.py b/Tetris/tetris/coords.py new file mode 100644 index 0000000..1b692a2 --- /dev/null +++ b/Tetris/tetris/coords.py @@ -0,0 +1,265 @@ +# +# # Tetris Coordinate System(s) +# + +from enum import Enum, auto +from typing import List, Dict, Optional, Union, TypeVar, Tuple, Generic + +from pydantic.generics import GenericModel +from pydantic.dataclasses import dataclass + +# Local Imports +from .index import Index + +class Dir(Enum): + """ Enumerated 2-D Directions """ + + Horiz = "horiz" + Vert = "vert" + + +@dataclass +class DbUnits: + """ Distance Specified in Database Units """ + + num: int + + +# impl HasUnits for DbUnits { +# # Every so often we need the raw number, fine. Use sparingly. +# #[inline(always)] +# def raw(self) -> int { +# self.0 +# } +# } +# impl std.ops.Div for DbUnits { +# type Output = int; +# def div(self, rhs: DbUnits) -> Self.Output { +# self.raw() / rhs.raw() +# } +# } +# impl std.ops.Div for DbUnits { +# type Output = Self; +# def div(self, rhs: int) -> Self.Output { +# Self(self.raw() / rhs) +# } +# } +# impl std.ops.Rem for DbUnits { +# type Output = int; +# def rem(self, rhs: DbUnits) -> Self.Output { +# self.raw().rem(rhs.raw()) +# } +# } +# impl std.ops.Mul for DbUnits { +# type Output = Self; +# def mul(self, rhs: int) -> Self.Output { +# Self(self.0 * rhs) +# } +# } +# impl std.ops.Mul for DbUnits { +# type Output = Self; +# def mul(self, rhs: usize) -> Self.Output { +# Self(int.try_from(rhs).unwrap() * self.0) +# } +# } + + +@dataclass +class PrimPitches: + """ Distance in Primitive-Pitches, in Either X/Y Direction """ + + dir: Dir + num: int + + @staticmethod + def new(dir: Dir, num: int) -> "PrimPitches": + # Create a new [PrimPitches] + return PrimPitches(dir, num) + + # Create a [PrimPitches] in the `x` direction + def x(num: int) -> "PrimPitches": + return PrimPitches(Dir.Horiz, num) + + # Create a [PrimPitches] in the `y` direction + def y(num: int) -> "PrimPitches": + return PrimPitches(Dir.Vert, num) + + # Create a new [PrimPitches] with opposite sign of `self.num` + def negate(self) -> "PrimPitches": + return PrimPitches(self.dir, -self.num) + + +# # Numeric operations between primitive-pitch values. +# # Generally panic if operating on two [PrimPitches] with different directions. +# impl std.ops.Add for PrimPitches { +# type Output = PrimPitches; +# def add(self, rhs: Self) -> Self.Output { +# if self.dir != rhs.dir { +# panic!( +# "Invalid attempt to add opposite-direction {:?} and {:?}", +# self, rhs +# ); +# } +# Self { +# dir: self.dir, +# num: self.num + rhs.num, +# } +# } +# } +# impl std.ops.AddAssign for PrimPitches { +# def add_assign( self, rhs: Self) { +# *self = *self + rhs; +# } +# } +# impl std.ops.Sub for PrimPitches { +# type Output = PrimPitches; +# def sub(self, rhs: Self) -> Self.Output { +# if self.dir != rhs.dir { +# panic!( +# "Invalid attempt to add opposite-direction {:?} and {:?}", +# self, rhs +# ); +# } +# Self { +# dir: self.dir, +# num: self.num - rhs.num, +# } +# } +# } +# impl std.ops.SubAssign for PrimPitches { +# def sub_assign( self, rhs: Self) { +# *self = *self - rhs; +# } +# } +# # Numeric operations between primitive-pitch values and regular numerics. +# impl std.ops.Mul for PrimPitches { +# type Output = Self; +# def mul(self, rhs: int) -> Self.Output { +# Self(self.dir, self.num * rhs) +# } +# } +# impl std.ops.MulAssign for PrimPitches { +# def mul_assign( self, rhs: int) { +# self.num = self.num * rhs; +# } +# } +# impl std.ops.Mul for PrimPitches { +# type Output = Self; +# def mul(self, rhs: usize) -> Self.Output { +# Self(self.dir, self.num * int.try_from(rhs).unwrap()) +# } +# } +# impl std.ops.MulAssign for PrimPitches { +# def mul_assign( self, rhs: usize) { +# self.num = self.num * int.try_from(rhs).unwrap(); +# } +# } + + +class LayerPitches: + """ Distance in Pitches on a Particular Layer """ + + layer: Index + num: int + + # Consume self, returning the underlying [usize] layer-index and [int] number. + def into_inner(self) -> Tuple[Index, int]: + return (self.layer, self.num) + + +# # Numeric operations between pitch-values and regular numerics. +# impl std.ops.Mul for LayerPitches { +# type Output = Self; +# def mul(self, rhs: int) -> Self.Output { +# Self(self.layer, self.num * rhs) +# } +# } +# impl std.ops.MulAssign for LayerPitches { +# def mul_assign( self, rhs: int) { +# self.num = self.num * rhs; +# } +# } +# impl std.ops.Mul for LayerPitches { +# type Output = Self; +# def mul(self, rhs: usize) -> Self.Output { +# Self(self.layer, self.num * int.try_from(rhs).unwrap()) +# } +# } +# impl std.ops.MulAssign for LayerPitches { +# def mul_assign( self, rhs: usize) { +# self.num = self.num * int.try_from(rhs).unwrap(); +# } +# } + + +class UnitType(Enum): + # Paired "type" zero-data enum for [UnitSpeced] + DbUnits = auto() + PrimPitches = auto() + LayerPitches = auto() + + +T = TypeVar("T") + + +class Xy(GenericModel, Generic[T]): + """ X-Y Cartesian Pair """ + + x: T + y: T + + def transpose(self) -> "Xy": + # Create a new [Xy] with transposed coordinates. + Xy(self.y, self.x) + + def dir(self, dir_: Dir) -> T: + """ Get the dimension in direction `dir` + Also available via square-bracket access through `__getitem__`. """ + if dir_ == Dir.Horiz: + return self.x + if dir_ == Dir.Vert: + return self.y + raise ValueError + + def __getitem__(self, dir_: Dir) -> T: + """ Square bracket access via [Dir] """ + if not isinstance(dir_, Dir): + return NotImplemented + return self.dir(dir_) + + +# impl From<(int, int)> for Xy { +# def from(tup: (int, int)) -> Self { +# Self { +# x: tup.0.into(), +# y: tup.1.into(), +# } +# } +# } +# impl From<(int, int)> for Xy { +# def from(tup: (int, int)) -> Self { +# Self( +# PrimPitches { +# dir: Dir.Horiz, +# num: tup.0.into(), +# }, +# PrimPitches { +# dir: Dir.Vert, +# num: tup.1.into(), +# }, +# ) +# } +# } + + +# # Unit-Specified Distances Enumeration +# +# Much of the confusion in a multi-coordinate system such as this +# lies in keeping track of which numbers are in which units. +# +# There are three generally useful units of measure here: +# * DB Units generally correspond to physical length quantities, e.g. nanometers +# * Primitive pitches +# * Per-layer pitches, parameterized by a metal-layer index +# +UnitSpeced = Union[DbUnits, PrimPitches, LayerPitches] diff --git a/Tetris/tetris/coords.rs b/Tetris/tetris/coords.rs deleted file mode 100644 index 5dd0d59..0000000 --- a/Tetris/tetris/coords.rs +++ /dev/null @@ -1,359 +0,0 @@ -//! -//! # Tetris Coordinate System(s) -//! - -// Std-lib imports -use std::convert::TryFrom; -use std::fmt::Debug; - -// Crates.io -use derive_more::{Add, AddAssign, DivAssign, From, MulAssign, Sub, SubAssign, Sum}; -use serde::{Deserialize, Serialize}; - -// Local imports -use crate::raw::Dir; - -/// # Location Integer Type-Alias -/// -/// Many internal fields are conceptually unsigned integers, but also undergo lots of math. -/// Rather than converting at each call-site, most are converted to [Int] and value-checked at creation time. -/// -/// Unsigned integers ([usize]) are generally used for indices, such as where the [Index] trait accepts them. -pub type Int = isize; - -/// # Unit-Specified Distances Enumeration -/// -/// Much of the confusion in a multi-coordinate system such as this -/// lies in keeping track of which numbers are in which units. -/// -/// There are three generally useful units of measure here: -/// * DB Units generally correspond to physical length quantities, e.g. nanometers -/// * Primitive pitches -/// * Per-layer pitches, parameterized by a metal-layer index -/// -#[derive(From, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum UnitSpeced { - /// Database Units, e.g. nanometers - DbUnits(DbUnits), - /// Primitive Pitches, parameterized by direction - PrimPitches(PrimPitches), - /// Per-Layer Pitches, parameterized by a metal-layer index - LayerPitches(LayerPitches), -} -/// # HasUnits -/// -/// A trait for types that have a unit-speced value. -/// Largely synonymous with being a variant of [UnitSpeced]. -/// -pub trait HasUnits: Clone + Copy { - /// Retrieve the raw number being spec'ed. Used sparingly, e.g. for exports. - fn raw(&self) -> Int; -} -impl HasUnits for UnitSpeced { - /// Dispatch the `raw` method to each variant - fn raw(&self) -> Int { - match self { - UnitSpeced::DbUnits(x) => x.raw(), - UnitSpeced::PrimPitches(x) => x.raw(), - UnitSpeced::LayerPitches(x) => x.raw(), - } - } -} - -/// A Scalar Value in Database Units -#[derive( - From, - Add, - AddAssign, - Sub, - SubAssign, - MulAssign, - DivAssign, - Sum, - Debug, - Default, - Clone, - Copy, - Serialize, - Deserialize, - PartialEq, - Eq, - PartialOrd, - Ord, -)] -pub struct DbUnits(pub Int); -impl HasUnits for DbUnits { - /// Every so often we need the raw number, fine. Use sparingly. - #[inline(always)] - fn raw(&self) -> Int { - self.0 - } -} -impl std::ops::Div for DbUnits { - type Output = Int; - fn div(self, rhs: DbUnits) -> Self::Output { - self.raw() / rhs.raw() - } -} -impl std::ops::Div for DbUnits { - type Output = Self; - fn div(self, rhs: Int) -> Self::Output { - Self(self.raw() / rhs) - } -} -impl std::ops::Rem for DbUnits { - type Output = Int; - fn rem(self, rhs: DbUnits) -> Self::Output { - self.raw().rem(rhs.raw()) - } -} -impl std::ops::Mul for DbUnits { - type Output = Self; - fn mul(self, rhs: Int) -> Self::Output { - Self(self.0 * rhs) - } -} -impl std::ops::Mul for DbUnits { - type Output = Self; - fn mul(self, rhs: usize) -> Self::Output { - Self(Int::try_from(rhs).unwrap() * self.0) - } -} - -/// A Scalar Value in Primitive-Pitches -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub struct PrimPitches { - pub dir: Dir, - pub num: Int, -} -impl PrimPitches { - /// Create a new [PrimPitches] - pub fn new(dir: Dir, num: Int) -> Self { - Self { dir, num } - } - /// Create a [PrimPitches] in the `x` direction - pub fn x(num: Int) -> Self { - Self::new(Dir::Horiz, num) - } - /// Create a [PrimPitches] in the `y` direction - pub fn y(num: Int) -> Self { - Self::new(Dir::Vert, num) - } - /// Create a new [PrimPitches] with opposite sign of `self.num` - pub fn negate(&self) -> Self { - Self::new(self.dir, -self.num) - } -} -impl HasUnits for PrimPitches { - /// Every so often we need the raw number, fine. Use sparingly. - #[inline(always)] - fn raw(&self) -> Int { - self.num - } -} -/// Numeric operations between primitive-pitch values. -/// Generally panic if operating on two [PrimPitches] with different directions. -impl std::ops::Add for PrimPitches { - type Output = PrimPitches; - fn add(self, rhs: Self) -> Self::Output { - if self.dir != rhs.dir { - panic!( - "Invalid attempt to add opposite-direction {:?} and {:?}", - self, rhs - ); - } - Self { - dir: self.dir, - num: self.num + rhs.num, - } - } -} -impl std::ops::AddAssign for PrimPitches { - fn add_assign(&mut self, rhs: Self) { - *self = *self + rhs; - } -} -impl std::ops::Sub for PrimPitches { - type Output = PrimPitches; - fn sub(self, rhs: Self) -> Self::Output { - if self.dir != rhs.dir { - panic!( - "Invalid attempt to add opposite-direction {:?} and {:?}", - self, rhs - ); - } - Self { - dir: self.dir, - num: self.num - rhs.num, - } - } -} -impl std::ops::SubAssign for PrimPitches { - fn sub_assign(&mut self, rhs: Self) { - *self = *self - rhs; - } -} -/// Numeric operations between primitive-pitch values and regular numerics. -impl std::ops::Mul for PrimPitches { - type Output = Self; - fn mul(self, rhs: Int) -> Self::Output { - Self::new(self.dir, self.num * rhs) - } -} -impl std::ops::MulAssign for PrimPitches { - fn mul_assign(&mut self, rhs: Int) { - self.num = self.num * rhs; - } -} -impl std::ops::Mul for PrimPitches { - type Output = Self; - fn mul(self, rhs: usize) -> Self::Output { - Self::new(self.dir, self.num * Int::try_from(rhs).unwrap()) - } -} -impl std::ops::MulAssign for PrimPitches { - fn mul_assign(&mut self, rhs: usize) { - self.num = self.num * Int::try_from(rhs).unwrap(); - } -} - -/// A Scalar Value in Layer-Pitches -#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub struct LayerPitches { - layer: usize, - num: Int, -} -impl LayerPitches { - /// Create a new [LayerPitches] on layer (index) `layer` - pub fn new(layer: usize, num: Int) -> Self { - Self { layer, num } - } - /// Consume self, returning the underlying [usize] layer-index and [Int] number. - pub fn into_inner(self) -> (usize, Int) { - (self.layer, self.num) - } -} -impl HasUnits for LayerPitches { - /// Every so often we need the raw number, fine. Use sparingly. - #[inline(always)] - fn raw(&self) -> Int { - self.num - } -} -/// Numeric operations between pitch-values and regular numerics. -impl std::ops::Mul for LayerPitches { - type Output = Self; - fn mul(self, rhs: Int) -> Self::Output { - Self::new(self.layer, self.num * rhs) - } -} -impl std::ops::MulAssign for LayerPitches { - fn mul_assign(&mut self, rhs: Int) { - self.num = self.num * rhs; - } -} -impl std::ops::Mul for LayerPitches { - type Output = Self; - fn mul(self, rhs: usize) -> Self::Output { - Self::new(self.layer, self.num * Int::try_from(rhs).unwrap()) - } -} -impl std::ops::MulAssign for LayerPitches { - fn mul_assign(&mut self, rhs: usize) { - self.num = self.num * Int::try_from(rhs).unwrap(); - } -} - -/// Paired "type" zero-data enum for [UnitSpeced] -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum UnitType { - DbUnits, - PrimPitches, - LayerPitches, -} - -/// Common geometric pairing of (x,y) coordinates -/// Represents points, sizes, rectangles, and anything else that pairs `x` and `y` fields. -/// *Only* instantiable with [HasUnits] data. -#[derive( - Debug, - Clone, - Copy, - Serialize, - Deserialize, - PartialEq, - Eq, - From, - Add, - AddAssign, - Sub, - SubAssign, - MulAssign, - DivAssign, - Sum, -)] -/// X-Y Cartesian Pair -pub struct Xy { - pub x: T, - pub y: T, -} -impl Xy { - /// Create a new [Xy]. - pub fn new(x: T, y: T) -> Xy { - Self { x, y } - } - /// Get the dimension in direction `dir` - /// Also available via the [Index] trait. - pub fn dir(&self, dir: Dir) -> &T { - match dir { - Dir::Horiz => &self.x, - Dir::Vert => &self.y, - } - } -} -impl Xy { - /// Create a new [Xy] with transposed coordinates. - pub fn transpose(&self) -> Xy { - Self { - y: self.x.clone(), - x: self.y.clone(), - } - } -} -impl Xy { - /// Get a non-unit-spec'ed raw integer [Xy] - pub fn raw(&self) -> Xy { - Xy::new(self.x.raw(), self.y.raw()) - } -} -impl std::ops::Index for Xy { - type Output = T; - fn index(&self, dir: Dir) -> &Self::Output { - match dir { - Dir::Horiz => &self.x, - Dir::Vert => &self.y, - } - } -} -impl From<(Int, Int)> for Xy { - fn from(tup: (Int, Int)) -> Self { - Self { - x: tup.0.into(), - y: tup.1.into(), - } - } -} -impl From<(Int, Int)> for Xy { - fn from(tup: (Int, Int)) -> Self { - Self::new( - PrimPitches { - dir: Dir::Horiz, - num: tup.0.into(), - }, - PrimPitches { - dir: Dir::Vert, - num: tup.1.into(), - }, - ) - } -} diff --git a/Tetris/tetris/error.py b/Tetris/tetris/error.py new file mode 100644 index 0000000..4c3a134 --- /dev/null +++ b/Tetris/tetris/error.py @@ -0,0 +1,8 @@ +""" +# Layout Error Type +""" + + +class LayoutError(Exception): + ... # FIXME! Add some real data + diff --git a/Tetris/tetris/index.py b/Tetris/tetris/index.py new file mode 100644 index 0000000..661a6d9 --- /dev/null +++ b/Tetris/tetris/index.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass + + +from pydantic.dataclasses import dataclass + + +@dataclass +class Index: + inner: int + diff --git a/Tetris/tetris/instance.py b/Tetris/tetris/instance.py new file mode 100644 index 0000000..872aa8e --- /dev/null +++ b/Tetris/tetris/instance.py @@ -0,0 +1,66 @@ +# +# # Instance Structures +# +# Located, oriented instances of other cells or similar reusable layout objects. +# + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import PrimPitches, Xy, Dir +from .error import LayoutError + +# from .cell import Cell +# from .place import Place + +# Instance of another Cell +@dataclass +class Instance: + # Instance Name + inst_name: str + # Cell Definition Reference + cell: "Cell" + # Location of the Instance origin + # This origin-position holds regardless of either `reflect` field. + # If specified in absolute coordinates location-units are [PrimPitches]. + loc: "Place[Xy[PrimPitches]]" + # Horizontal Reflection + reflect_horiz: bool + # Vertical Reflection + reflect_vert: bool + + # Boolean indication of whether this Instance is reflected in direction `dir` + def reflected(self, dir_: Dir) -> bool: + if dir_ == Dir.Horiz: + return self.reflect_horiz + if dir_ == Dir.Vert: + return self.reflect_vert + raise ValueError + + # Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. + def boundbox_size(self) -> "Xy[PrimPitches]": + return self.cell.boundbox_size() + + def __repr__(self): + return f"Instance(name=:self.inst_name, cell=:self.cell.name, loc=:self.loc)" + + # Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. + # Instance location must be resolved to absolute coordinates, or this method will fail. + def boundbox(self) -> "BoundBox[PrimPitches]": + from .bbox import BoundBox + + loc = self.loc.abs() + outline = self.cell.outline() + + if self.reflect_horiz: + (x0, x1) = ((loc.x - outline.xmax(), loc.x),) + else: + (x0, x1) = ((loc.x, loc.x + outline.xmax()),) + + if self.reflect_vert: + (y0, y1) = ((loc.y - outline.ymax(), loc.y),) + else: + (y0, y1) = ((loc.y, loc.y + outline.ymax()),) + + return BoundBox(Xy(x0, y0), Xy(x1, y1)) + diff --git a/Tetris/tetris/instance.rs b/Tetris/tetris/instance.rs deleted file mode 100644 index 6a26ca9..0000000 --- a/Tetris/tetris/instance.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! -//! # Instance Structures -//! -//! Located, oriented instances of other cells or similar reusable layout objects. -//! - -// Local imports -use crate::bbox::{BoundBox, HasBoundBox}; -use crate::cell::Cell; -use crate::coords::{PrimPitches, Xy}; -use crate::placement::Place; -use crate::raw::{Dir, LayoutError, LayoutResult}; -use crate::utils::Ptr; - -/// Instance of another Cell -#[derive(Debug, Clone)] -pub struct Instance { - /// Instance Name - pub inst_name: String, - /// Cell Definition Reference - pub cell: Ptr, - /// Location of the Instance origin - /// This origin-position holds regardless of either `reflect` field. - /// If specified in absolute coordinates, location-units are [PrimPitches]. - pub loc: Place>, - /// Horizontal Reflection - pub reflect_horiz: bool, - /// Vertical Reflection - pub reflect_vert: bool, -} -impl Instance { - /// Boolean indication of whether this Instance is reflected in direction `dir` - pub fn reflected(&self, dir: Dir) -> bool { - match dir { - Dir::Horiz => self.reflect_horiz, - Dir::Vert => self.reflect_vert, - } - } - /// Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. - pub fn boundbox_size(&self) -> LayoutResult> { - let cell = self.cell.read()?; - cell.boundbox_size() - } -} -impl std::fmt::Display for Instance { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let cell_name = { - let cell = self.cell.read().unwrap(); - cell.name.clone() - }; - write!( - f, - "Instance(name={}, cell={}, loc={:?})", - self.inst_name, cell_name, self.loc - ) - } -} -impl HasBoundBox for Instance { - type Units = PrimPitches; - type Error = LayoutError; - /// Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. - /// Instance location must be resolved to absolute coordinates, or this method will fail. - fn boundbox(&self) -> LayoutResult> { - let loc = self.loc.abs()?; - let cell = self.cell.read()?; - let outline = cell.outline()?; - let (x0, x1) = match self.reflect_horiz { - false => (loc.x, loc.x + outline.xmax()), - true => (loc.x - outline.xmax(), loc.x), - }; - let (y0, y1) = match self.reflect_vert { - false => (loc.y, loc.y + outline.ymax()), - true => (loc.y - outline.ymax(), loc.y), - }; - Ok(BoundBox::new(Xy::new(x0, y0), Xy::new(x1, y1))) - } -} diff --git a/Tetris/tetris/stack.rs b/Tetris/tetris/layer_period.py similarity index 52% rename from Tetris/tetris/stack.rs rename to Tetris/tetris/layer_period.py index b7baf54..b3fb718 100644 --- a/Tetris/tetris/stack.rs +++ b/Tetris/tetris/layer_period.py @@ -11,57 +11,67 @@ use crate::utils::Ptr; use crate::{tracks::*, validate}; -/// # Stack -/// -/// The z-stack, primarily including metal, via, and primitive layers -#[derive(Debug, Clone)] -pub struct Stack { - /// Measurement units - pub units: Units, - /// Primitive Layer - pub prim: PrimitiveLayer, - /// Set of metal layers - pub metals: Vec, - /// Set of via layers - pub vias: Vec, - /// [raw::Layer] Mappings - pub rawlayers: Option>, - /// Layer used for cell outlines/ boundaries - pub boundary_layer: Option, + + +/// Transformed single period of [Track]s on a [Layer] +/// Splits track-info between signals and rails. +/// Stores each as a [Track] struct, which moves to a (start, width) size-format, +/// and includes a vector of track-segments for cutting and assigning nets. +#[derive(Debug, Clone, Default)] +pub struct LayerPeriod<'lib> { + pub index: usize, + pub signals: Vec>, + pub rails: Vec>, } -impl Stack { - /// Run validation, consuming `self` and creating a [validate::ValidStack] - pub fn validate(self) -> LayoutResult { - validate::validate_stack(self) +impl<'lib> LayerPeriod<'lib> { + /// Shift the period by `dist` in its periodic direction + pub fn offset(&mut self, dist: DbUnits) -> LayoutResult<()> { + for t in self.rails.iter_mut() { + t.data.start += dist; + } + for t in self.signals.iter_mut() { + t.data.start += dist; + } + Ok(()) + } + /// Set the stop position for all [Track]s to `stop` + pub fn stop(&mut self, stop: DbUnits) -> LayoutResult<()> { + for t in self.rails.iter_mut() { + t.stop(stop)?; + } + for t in self.signals.iter_mut() { + t.stop(stop)?; + } + Ok(()) + } + /// Cut all [Track]s from `start` to `stop`, + pub fn cut( + &mut self, + start: DbUnits, + stop: DbUnits, + src: &'lib TrackCross, + ) -> TrackResult<()> { + for t in self.rails.iter_mut() { + t.cut(start, stop, src)?; + } + for t in self.signals.iter_mut() { + t.cut(start, stop, src)?; + } + Ok(()) + } + /// Block all [Track]s from `start` to `stop`, + pub fn block(&mut self, start: DbUnits, stop: DbUnits, src: &Ptr) -> TrackResult<()> { + for t in self.rails.iter_mut() { + t.block(start, stop, src)?; + } + for t in self.signals.iter_mut() { + t.block(start, stop, src)?; + } + Ok(()) } } -/// # MetalLayer -/// -/// Metal layer in a [Stack] -/// Each layer is effectively infinite-spanning in one dimension, and periodic in the other. -/// Layers with `dir=Dir::Horiz` extend to infinity in x, and repeat in y, and vice-versa. -/// -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MetalLayer { - /// Layer Name - pub name: String, - /// Direction Enumeration (Horizontal/ Vertical) - pub dir: Dir, - /// Default size of wire-cuts - pub cutsize: DbUnits, - /// Track Size & Type Entries - pub entries: Vec, - /// Offset, in our periodic dimension - pub offset: DbUnits, - /// Overlap between periods - pub overlap: DbUnits, - /// Setting for period-by-period flipping - pub flip: FlipMode, - /// Primitive-layer relationship - pub prim: PrimitiveMode, - /// [raw::Layer] for exports - pub raw: Option, -} + + #[derive(Debug, Clone, Default)] pub struct LayerPeriodData { pub signals: Vec, @@ -162,187 +172,4 @@ cursor += d; } Ok(period) - } - /// Flatten our [Entry]s into a vector - /// Removes any nested patterns - pub(crate) fn entries(&self) -> Vec { - let mut v: Vec = Vec::new(); - for e in self.entries.iter() { - match e { - TrackSpec::Entry(ee) => v.push(ee.clone()), - // FIXME: why doesn't this recursively call `entries`? Seems it could/should. - TrackSpec::Repeat(p) => { - for _i in 0..p.nrep { - for ee in p.entries.iter() { - v.push(ee.clone()); - } - } - } - } - } - v - } - /// Sum up this [Layer]'s pitch - pub(crate) fn pitch(&self) -> DbUnits { - self.entries().iter().map(|e| e.width).sum::() - self.overlap - } -} - -/// Transformed single period of [Track]s on a [Layer] -/// Splits track-info between signals and rails. -/// Stores each as a [Track] struct, which moves to a (start, width) size-format, -/// and includes a vector of track-segments for cutting and assigning nets. -#[derive(Debug, Clone, Default)] -pub struct LayerPeriod<'lib> { - pub index: usize, - pub signals: Vec>, - pub rails: Vec>, -} -impl<'lib> LayerPeriod<'lib> { - /// Shift the period by `dist` in its periodic direction - pub fn offset(&mut self, dist: DbUnits) -> LayoutResult<()> { - for t in self.rails.iter_mut() { - t.data.start += dist; - } - for t in self.signals.iter_mut() { - t.data.start += dist; - } - Ok(()) - } - /// Set the stop position for all [Track]s to `stop` - pub fn stop(&mut self, stop: DbUnits) -> LayoutResult<()> { - for t in self.rails.iter_mut() { - t.stop(stop)?; - } - for t in self.signals.iter_mut() { - t.stop(stop)?; - } - Ok(()) - } - /// Cut all [Track]s from `start` to `stop`, - pub fn cut( - &mut self, - start: DbUnits, - stop: DbUnits, - src: &'lib TrackCross, - ) -> TrackResult<()> { - for t in self.rails.iter_mut() { - t.cut(start, stop, src)?; - } - for t in self.signals.iter_mut() { - t.cut(start, stop, src)?; - } - Ok(()) - } - /// Block all [Track]s from `start` to `stop`, - pub fn block(&mut self, start: DbUnits, stop: DbUnits, src: &Ptr) -> TrackResult<()> { - for t in self.rails.iter_mut() { - t.block(start, stop, src)?; - } - for t in self.signals.iter_mut() { - t.block(start, stop, src)?; - } - Ok(()) - } -} -/// # Via / Insulator Layer Between Metals -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ViaLayer { - /// Layer name - pub name: String, - /// Top of the two layers connected by this layer - pub top: ViaTarget, - /// Bottom of the two layers connected by this layer - pub bot: ViaTarget, - /// Via size - pub size: Xy, - /// Stream-out layer numbers - pub raw: Option, -} -/// # Via Targets -/// -/// Enumerates the things vias can "go between". -/// Generally either a numbered metal layer, or the primitive base-layers. -/// -/// Values stored in the `Metal` variant are treated as indicies into `Stack.metals`, -/// i.e. `Metal(0)` is the first metal layer defined in the stack. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ViaTarget { - /// Connect to the Primitive layer - Primitive, - /// Connect to an indexed metal layer - Metal(usize), -} -impl From for ViaTarget { - fn from(i: usize) -> Self { - Self::Metal(i) - } -} -impl From> for ViaTarget { - fn from(i: Option) -> Self { - match i { - None => Self::Primitive, - Some(i) => Self::Metal(i), - } - } -} -/// Assignment of a net onto a track-intersection -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Assign { - /// Net Name - pub net: String, - /// Track Intersection Location - pub at: TrackCross, -} -impl Assign { - /// Create a new [Assign] - pub fn new(net: impl Into, at: impl Into) -> Self { - Self { - net: net.into(), - at: at.into(), - } - } -} -/// Relative Z-Axis Reference to one Layer `Above` or `Below` another -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum RelZ { - Above, - Below, -} -impl RelZ { - pub fn other(&self) -> Self { - match *self { - RelZ::Above => RelZ::Below, - RelZ::Below => RelZ::Above, - } - } -} - -/// Indication of whether a layer flips in its periodic axis with every period, -/// as most standard-cell-style logic gates do. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum FlipMode { - EveryOther, - None, -} -/// Indication of whether a layer is owned by, partially included in, or external to the primitive blocks -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum PrimitiveMode { - /// Owned by Primitives - Prim, - /// Partially split between Primitives and Stack - Split, - /// Owned by the Stack - Stack, -} -/// Description of the primitive-level cells in a [Stack] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct PrimitiveLayer { - pub pitches: Xy, -} -impl PrimitiveLayer { - /// Create a new [PrimitiveLayer] with the given pitches - pub fn new(pitches: Xy) -> Self { - Self { pitches } - } -} + } \ No newline at end of file diff --git a/Tetris/tetris/layout.py b/Tetris/tetris/layout.py new file mode 100644 index 0000000..f30f745 --- /dev/null +++ b/Tetris/tetris/layout.py @@ -0,0 +1,58 @@ +# +# # Layout-Cell Definitions +# +# Physical implementations of tetris [Cell]s. +# +from typing import List +from dataclasses import field + +from pydantic.dataclasses import dataclass + +# Local imports +from .outline import Outline +from .stack import Assign +from .relz import RelZ +from .track_spec import TrackCross +from .instance import Instance + +# from .placeable import Placeable + +# # Layout Cell Implementation +# +# A combination of lower-level cell instances and net-assignments to tracks. +# +@dataclass +class Layout: + # Cell Name + name: str + # Number of Metal Layers Used + metals: int + # Outline shape counted in x and y pitches of `stack` + outline: Outline + + # Layout Instances + instances: List[Instance] = field(default_factory=list) + # Net-to-track assignments + assignments: List[Assign] = field(default_factory=list) + # Track cuts + cuts: List[TrackCross] = field(default_factory=list) + # Placeable objects + places: List["Placeable"] = field(default_factory=list) + + # Assign a net at the given coordinates. + def assign( + self, net: str, layer: int, track: int, at: int, relz: RelZ, + ): + at = TrackCross.from_relz(layer, track, at, relz) + self.assignments.append(Assign(net, at)) + + # Add a cut at the specified coordinates. + def cut(self, layer: int, track: int, at: int, relz: RelZ): + cut = TrackCross.from_relz(layer, track, at, relz) + self.cuts.append(cut) + + # Get a temporary handle for net assignments + def net(self, net: str) -> "NetHandle": + from .net_handle import NetHandle + + return NetHandle(name=net, parent=self) diff --git a/Tetris/tetris/layout.rs b/Tetris/tetris/layout.rs deleted file mode 100644 index 391c920..0000000 --- a/Tetris/tetris/layout.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! -//! # Layout-Cell Definitions -//! -//! Physical implementations of tetris [Cell]s. -//! - -// Local imports -use crate::{ - instance::Instance, - outline, - placement::Placeable, - stack::{Assign, RelZ}, - tracks::TrackCross, - utils::PtrList, -}; - -/// # Layout Cell Implementation -/// -/// A combination of lower-level cell instances and net-assignments to tracks. -/// -#[derive(Debug, Clone, Builder)] -#[builder(pattern = "owned", setter(into))] -pub struct Layout { - /// Cell Name - pub name: String, - /// Number of Metal Layers Used - pub metals: usize, - /// Outline shape, counted in x and y pitches of `stack` - pub outline: outline::Outline, - - /// Layout Instances - #[builder(default)] - pub instances: PtrList, - /// Net-to-track assignments - #[builder(default)] - pub assignments: Vec, - /// Track cuts - #[builder(default)] - pub cuts: Vec, - /// Placeable objects - #[builder(default)] - pub places: Vec, -} -impl Layout { - /// Create a new [Layout] - pub fn new(name: impl Into, metals: usize, outline: outline::Outline) -> Self { - let name = name.into(); - Layout { - name, - metals, - outline, - instances: PtrList::new(), - assignments: Vec::new(), - cuts: Vec::new(), - places: Vec::new(), - } - } - /// Create a [LayoutBuilder], a struct created by the [Builder] macro. - pub fn builder() -> LayoutBuilder { - LayoutBuilder::default() - } - /// Assign a net at the given coordinates. - pub fn assign( - &mut self, - net: impl Into, - layer: usize, - track: usize, - at: usize, - relz: RelZ, - ) { - let net = net.into(); - let at = TrackCross::from_relz(layer, track, at, relz); - self.assignments.push(Assign { net, at }) - } - /// Add a cut at the specified coordinates. - pub fn cut(&mut self, layer: usize, track: usize, at: usize, relz: RelZ) { - let cut = TrackCross::from_relz(layer, track, at, relz); - self.cuts.push(cut) - } - /// Get a temporary handle for net assignments - pub fn net<'h>(&'h mut self, net: impl Into) -> NetHandle<'h> { - let name = net.into(); - NetHandle { name, parent: self } - } -} -/// # Net Handle -/// -/// A short-term handle for chaining multiple assignments to a net -/// Typically used as: `mycell.net("name").at(/* args */).at(/* more args */)` -/// Takes an exclusive reference to its parent [Layout], -/// so generally must be dropped quickly to avoid locking it up. -/// -pub struct NetHandle<'h> { - name: String, - parent: &'h mut Layout, -} -impl<'h> NetHandle<'h> { - /// Assign our net at the given coordinates. - /// Consumes and returns `self` to enable chaining. - pub fn at(self, layer: usize, track: usize, at: usize, relz: RelZ) -> Self { - self.parent.assign(&self.name, layer, track, at, relz); - self - } -} diff --git a/Tetris/tetris/lib.rs b/Tetris/tetris/lib.rs deleted file mode 100644 index 00532c3..0000000 --- a/Tetris/tetris/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! -//! # Layout21 "Tetris" Semi-Custom Layout System -//! - -// External macro usages -#[macro_use] -extern crate derive_builder; - -// Modules -pub mod abs; -pub mod array; -pub mod bbox; -pub mod cell; -pub mod conv; -pub mod coords; -pub mod group; -pub mod instance; -pub mod interface; -pub mod layout; -pub mod library; -pub mod outline; -pub mod placement; -pub mod placer; -pub mod stack; -pub mod tracks; -pub mod validate; - -// Re-exports -pub use layout21protos as protos; -pub use layout21raw as raw; -pub use layout21utils as utils; - -/// Unit Tests Module -#[cfg(test)] -mod tests; diff --git a/Tetris/tetris/library.py b/Tetris/tetris/library.py new file mode 100644 index 0000000..d248f8b --- /dev/null +++ b/Tetris/tetris/library.py @@ -0,0 +1,74 @@ +# +# # Layout Library Module +# + +from typing import List, Set, Dict + +from pydantic.dataclasses import dataclass + +from .cell import Cell + + +# # Layout Library +# +# A combination of cell definitions, sub-libraries, and metadata +# +@dataclass +class Library: + # Library Name + name: str + # Cell Definitions + cells: Dict[str, Cell] + + # FIXME: `raw` stuff + # # [raw.Library] Definitions + # rawlibs: List[raw.Library] + # # Export to a [raw.Library] + # def to_raw(self, stack: validate.ValidStack) -> LayoutResult> : + # conv.raw.RawExporter.convert(self, stack) + # # Add a [raw.Library] + # def add_rawlib(self, rawlib: raw.Library) -> Ptr : + # self.rawlibs.insert(rawlib) + + # Add a [Cell] + def add_cell(self, cell: Cell) -> None: + self.cells.append(cell) + + # Create an ordered list in which dependent cells follow their dependencies. + def dep_order(self) -> List[Cell]: + return DepOrder.order(self) + + +# # Dependency-Orderer +# +# Creates an ordered list in which dependent cells follow their dependencies. +# FIXME: migrate to utils.DepOrder +# +@dataclass +class DepOrder: + lib: Library + stack: List[Cell] + seen: Set[Cell] # FIXME! hashing of these aint gonna happen + + def order(lib: Library) -> List[Cell]: + myself = DepOrder(lib=lib, stack=list(), seen=set()) + for cell in myself.lib.cells.keys(): + myself.push(cell) + return myself.stack + + def push(self, cellname: str): + # If the Cell hasn't already been visited, depth-first search it + if cellname in self.seen: + return # Already done + + # Read the cell-pointer + cell = self.lib.cells[cellname] + # If the cell has an implementation, visit its [Instance]s before inserting it + if cell.layout is not None: + for inst in cell.layout.instances: + self.push(inst.cell.name) + + # And insert the cell (pointer) itself + self.seen.insert(cellname) + self.stack.push(cell) + diff --git a/Tetris/tetris/library.rs b/Tetris/tetris/library.rs deleted file mode 100644 index 3496c9a..0000000 --- a/Tetris/tetris/library.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! -//! # Layout Library Module -//! - -// Std-lib -use std::collections::HashSet; - -// Local imports -use crate::raw::LayoutResult; -use crate::utils::{Ptr, PtrList}; -use crate::{cell, conv, raw, validate}; - -/// # Layout Library -/// -/// A combination of cell definitions, sub-libraries, and metadata -/// -#[derive(Debug, Clone, Default)] -pub struct Library { - /// Library Name - pub name: String, - /// Cell Definitions - pub cells: PtrList, - /// [raw::Library] Definitions - pub rawlibs: PtrList, -} -impl Library { - /// Create a new and initially empty [Library] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - ..Default::default() - } - } - /// Export to a [raw::Library] - pub fn to_raw(self, stack: validate::ValidStack) -> LayoutResult> { - conv::raw::RawExporter::convert(self, stack) - } - /// Add a [Cell] - pub fn add_cell(&mut self, cell: cell::Cell) -> Ptr { - self.cells.insert(cell) - } - /// Add a [raw::Library] - pub fn add_rawlib(&mut self, rawlib: raw::Library) -> Ptr { - self.rawlibs.insert(rawlib) - } - /// Create an ordered list in which dependent cells follow their dependencies. - pub fn dep_order(&self) -> Vec> { - DepOrder::order(self) - } -} - -/// # Dependency-Orderer -/// -/// Creates an ordered list in which dependent cells follow their dependencies. -/// FIXME: migrate to utils::DepOrder -/// -#[derive(Debug)] -pub struct DepOrder<'lib> { - lib: &'lib Library, - stack: Vec>, - seen: HashSet>, -} -impl<'lib> DepOrder<'lib> { - fn order(lib: &'lib Library) -> Vec> { - let mut myself = Self { - lib, - stack: Vec::new(), - seen: HashSet::new(), - }; - for cell in myself.lib.cells.iter() { - myself.push(cell); - } - myself.stack - } - fn push(&mut self, ptr: &Ptr) { - // If the Cell hasn't already been visited, depth-first search it - if !self.seen.contains(&ptr) { - // Read the cell-pointer - let cell = ptr.read().unwrap(); - // If the cell has an implementation, visit its [Instance]s before inserting it - if let Some(layout) = &cell.layout { - for ptr in layout.instances.iter() { - let inst = ptr.read().unwrap(); - self.push(&inst.cell); - } - } - // And insert the cell (pointer) itself - self.seen.insert(Ptr::clone(ptr)); - self.stack.push(Ptr::clone(ptr)); - } - } -} diff --git a/Tetris/tetris/net_handle.py b/Tetris/tetris/net_handle.py new file mode 100644 index 0000000..4704a11 --- /dev/null +++ b/Tetris/tetris/net_handle.py @@ -0,0 +1,25 @@ + +from pydantic.dataclasses import dataclass +from .layout import Layout +from .relz import RelZ + +# # Net Handle +# +# A short-term handle for chaining multiple assignments to a net +# Typically used as: `mycell.net("name").at(/* args */).at(/* more args */)` +# Takes an exclusive reference to its parent [Layout], +# so generally must be dropped quickly to avoid locking it up. +# +@dataclass +class NetHandle: + name: str + parent: Layout + + # Assign our net at the given coordinates. + # Consumes and returns `self` to enable chaining. + def at(self, layer: int, track: int, at: int, relz: RelZ) -> "NetHandle" { + self.parent.assign(&self.name, layer, track, at, relz); + return self + } +} + diff --git a/Tetris/tetris/outline.py b/Tetris/tetris/outline.py new file mode 100644 index 0000000..46ae9a2 --- /dev/null +++ b/Tetris/tetris/outline.py @@ -0,0 +1,86 @@ +# +# # Tetris-Cell Outlines +# + +from typing import List + +from pydantic.dataclasses import dataclass + +from .coords import PrimPitches, Dir +from .error import LayoutError + +# # Block Outline +# +# All block-outlines are "tetris shaped" rectilinear polygons, and are `layout21.tetris`'s namesake. +# +# These boundaries are closed, consist solely of 90-degree rectangular turns, +# and are specified by a counter-clockwise set of points. +# "Holes" such as the shapes "O" and "8" and "divots" such as the shapes "U" and "H" are not supported. +# +# Two equal-length vectors `x` and `y` describe an Outline's points. +# Counter-clockwise-ness and divot-free-ness requires that: +# * (a) `x` values are monotonically non-increasing, and +# * (b) `y` values are monotonically non-decreasing +# +# Such an outline has vertices in Cartesian space at: +# `[(0,0), (x[0], 0), (x[0], y[0]), (x[1], y[0]), ... , (0, y[-1]), (0,0)]` +# With the first point at the origin, the final point at (0, y[-1]), and its connection back to the origin all implied. +# +# Example: a rectangular Outline would requires single entry for each of `x` and `y`, +# at the rectangle's vertex opposite the origin in both axes. +# +@dataclass +class Outline: + x: List[PrimPitches] + y: List[PrimPitches] + + # Outline constructor from primitive-pitches + def from_prim_pitches(x: List[PrimPitches], y: List[PrimPitches]) -> "Outline": + # Check that x and y are of compatible lengths + if x.len() < 1 or x.len() != y.len(): + raise LayoutError("Invalid zero-length Outline dimensions") + + # Check for: + # * Correct directions + # * all non-negative values + for k in 0.0 .x.len(): + if x[k].dir != Dir.Horiz or y[k].dir != Dir.Vert: + raise LayoutError("Invalid Outline direction(s)") + + if x[k].num < 0 or y[k].num < 0: + raise LayoutError("Invalid Outline with negative coordinate(s)") + + # Check for: + # * x non-increasing-ness, + # * y for non-decreasing-ness + for k in 1.0 .x.len(): + if x[k].num > x[k - 1].num: + raise LayoutError("Invalid Outline with non-increasing x-coordinates") + + if y[k].num < y[k - 1].num: + raise LayoutError("Invalid Outline with non-decreasing y-coordinates") + + return Outline(x, y) + + @staticmethod + def rect(x: int, y: int) -> "Outline": + # Create a new rectangular outline of dimenions `x` by `y` + return Outline([x], [y]) + + # Maximum x-coordinate + # (Which is also always the *first* x-coordinate) + def xmax(self) -> PrimPitches: + self.x[0] + + # Maximum y-coordinate + # (Which is also always the *last* y-coordinate) + def ymax(self) -> PrimPitches: + self.y[self.y.len() - 1] + + # Maximum coordinate in [Dir] `dir` + def max(self, dir_: Dir) -> PrimPitches: + if dir_ == Dir.Horiz: + return self.xmax() + if dir_ == Dir.Vert: + return self.ymax() + raise ValueError diff --git a/Tetris/tetris/outline.rs b/Tetris/tetris/outline.rs deleted file mode 100644 index d562aa6..0000000 --- a/Tetris/tetris/outline.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! -//! # Tetris-Cell Outlines -//! - -// Std-lib imports -use std::fmt::Debug; - -// Crates.io -use serde::{Deserialize, Serialize}; - -// Local imports -use crate::coords::{Int, PrimPitches}; -use crate::raw::{Dir, LayoutError, LayoutResult}; - -/// # Block Outline -/// -/// All block-outlines are "tetris shaped" rectilinear polygons, and are `layout21::tetris`'s namesake. -/// -/// These boundaries are closed, consist solely of 90-degree rectangular turns, -/// and are specified by a counter-clockwise set of points. -/// "Holes" such as the shapes "O" and "8" and "divots" such as the shapes "U" and "H" are not supported. -/// -/// Two equal-length vectors `x` and `y` describe an Outline's points. -/// Counter-clockwise-ness and divot-free-ness requires that: -/// * (a) `x` values are monotonically non-increasing, and -/// * (b) `y` values are monotonically non-decreasing -/// -/// Such an outline has vertices in Cartesian space at: -/// `[(0,0), (x[0], 0), (x[0], y[0]), (x[1], y[0]), ... , (0, y[-1]), (0,0)]` -/// With the first point at the origin, the final point at (0, y[-1]), and its connection back to the origin all implied. -/// -/// Example: a rectangular Outline would requires single entry for each of `x` and `y`, -/// at the rectangle's vertex opposite the origin in both axes. -/// -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct Outline { - pub x: Vec, - pub y: Vec, -} -impl Outline { - /// Outline constructor, with inline checking for validity of `x` & `y` vectors - pub fn new(x: &[Int], y: &[Int]) -> LayoutResult { - // Convert into [PrimPitches] united-objects, and return a new Self. - let x = x.into_iter().map(|i| PrimPitches::x(*i)).collect(); - let y = y.into_iter().map(|i| PrimPitches::y(*i)).collect(); - Self::from_prim_pitches(x, y) - } - /// Outline constructor from primitive-pitches - pub fn from_prim_pitches(x: Vec, y: Vec) -> LayoutResult { - // Check that x and y are of compatible lengths - if x.len() < 1 || x.len() != y.len() { - LayoutError::fail("Invalid zero-length Outline dimensions")?; - } - // Check for: - // * Correct directions - // * all non-negative values - for k in 0..x.len() { - if x[k].dir != Dir::Horiz || y[k].dir != Dir::Vert { - LayoutError::fail("Invalid Outline direction(s)")?; - } - if x[k].num < 0 || y[k].num < 0 { - LayoutError::fail("Invalid Outline with negative coordinate(s)")?; - } - } - // Check for: - // * x non-increasing-ness, - // * y for non-decreasing-ness - for k in 1..x.len() { - if x[k].num > x[k - 1].num { - LayoutError::fail("Invalid Outline with non-increasing x-coordinates")?; - } - if y[k].num < y[k - 1].num { - LayoutError::fail("Invalid Outline with non-decreasing y-coordinates")?; - } - } - Ok(Self { x, y }) - } - /// Create a new rectangular outline of dimenions `x` by `y` - pub fn rect(x: Int, y: Int) -> LayoutResult { - Self::new(&[x], &[y]) - } - /// Maximum x-coordinate - /// (Which is also always the *first* x-coordinate) - pub fn xmax(&self) -> PrimPitches { - self.x[0] - } - /// Maximum y-coordinate - /// (Which is also always the *last* y-coordinate) - pub fn ymax(&self) -> PrimPitches { - self.y[self.y.len() - 1] - } - /// Maximum coordinate in [Dir] `dir` - pub fn max(&self, dir: Dir) -> PrimPitches { - match dir { - Dir::Horiz => self.xmax(), - Dir::Vert => self.ymax(), - } - } -} diff --git a/Tetris/tetris/relz.py b/Tetris/tetris/relz.py new file mode 100644 index 0000000..557ece7 --- /dev/null +++ b/Tetris/tetris/relz.py @@ -0,0 +1,16 @@ +from enum import Enum, auto + + +class RelZ(Enum): + """ Relative Z-Axis Reference to one Layer `Above` or `Below` another """ + + Above = auto() + Below = auto() + + def other(self) -> "RelZ": + if self == RelZ.Above: + return RelZ.Below + if self == RelZ.Below: + return RelZ.Above + raise ValueError + diff --git a/Tetris/tetris/stack.py b/Tetris/tetris/stack.py new file mode 100644 index 0000000..d775819 --- /dev/null +++ b/Tetris/tetris/stack.py @@ -0,0 +1,149 @@ +from enum import Enum, auto +from typing import List, Optional +from dataclasses import dataclass + +from pydantic.dataclasses import dataclass + +# Local imports +from .index import Index +from .coords import DbUnits, Xy, Dir +from .track_spec import TrackCross, TrackSpec, TrackEntry, Repeat + + +@dataclass +class Units: + ... # FIXME! + + +# # Via Targets +# +# Enumerates the things vias can "go between". +# Generally either a numbered metal layer, or the primitive base-layers. +# +# Values stored in the `Metal` variant are treated as indicies into `Stack.metals`, +# i.e. `Metal(0)` is the first metal layer defined in the stack. +ViaTarget = Optional[Index] + +# # Via / Insulator Layer Between Metals +@dataclass +class ViaLayer: + # Layer name + name: str + # Top of the two layers connected by this layer + top: ViaTarget + # Bottom of the two layers connected by this layer + bot: ViaTarget + # Via size + size: Xy[DbUnits] + + # FIXME: `raw` stuff + # # Stream-out layer numbers + # raw: Option + + +# Assignment of a net onto a track-intersection +@dataclass +class Assign: + # Net Name + net: str + # Track Intersection Location + at: TrackCross + + +# Indication of whether a layer flips in its periodic axis with every period, +# as most standard-cell-style logic gates do. +class FlipMode(Enum): + EveryOther = auto() + NoFlip = auto() + + +# Indication of whether a layer is owned by, partially included in, or external to the primitive blocks +class PrimitiveMode(Enum): + # Owned by Primitives + Prim = auto() + # Partially split between Primitives and Stack + Split = auto() + # Owned by the Stack + Stack = auto() + + +# Description of the primitive-level cells in a [Stack] +@dataclass +class PrimitiveLayer: + pitches: Xy[DbUnits] + + +# # MetalLayer +# +# Metal layer in a [Stack] +# Each layer is effectively infinite-spanning in one dimension, and periodic in the other. +# Layers with `dir=Dir::Horiz` extend to infinity in x, and repeat in y, and vice-versa. +# +@dataclass +class MetalLayer: + # Layer Name + name: str + # Direction Enumeration (Horizontal/ Vertical) + dir: Dir + # Default size of wire-cuts + cutsize: DbUnits + # Track Size Type Entries + entries: List[TrackSpec] + # Offset in our periodic dimension + offset: DbUnits + # Overlap between periods + overlap: DbUnits + # Setting for period-by-period flipping + flip: FlipMode + # Primitive-layer relationship + prim: PrimitiveMode + + # FIXME: handling `raw` stuff + # # [raw::Layer] for exports + # raw: Option + + # Sum up this [Layer]'s pitch + def pitch(self) -> DbUnits: + return sum(self.flatten()) - self.overlap + + # Flatten our [Entry]s into a vector + # Removes any nested patterns + def flatten(self) -> List[TrackEntry]: + v: List[TrackEntry] = list() + for e in self.entries: + if isinstance(e, Repeat): + for _ in range(e.nrep): + for ee in e.entries: + v.push(ee) + elif isinstance(e, TrackEntry): + v.push(e) + else: + raise TypeError + return v + + +# # Stack +# +# The z-stack, primarily including metal, via, and primitive layers +@dataclass +class Stack: + # Measurement units + units: Units + # Primitive Layer + prim: PrimitiveLayer + # Set of metal layers + metals: List[MetalLayer] + # Set of via layers + vias: List[ViaLayer] + + # FIXME: how to handle `raw` thing + # # [raw::Layer] Mappings + # rawlayers: Option> + # # Layer used for cell outlines/ boundaries + # boundary_layer: Optional + + # Run validation, creating a [ValidStack] + def validate(self) -> "ValidStack": + from .validate import validate_stack + + return validate_stack(self) diff --git a/Tetris/tetris/track_spec.py b/Tetris/tetris/track_spec.py new file mode 100644 index 0000000..9a20940 --- /dev/null +++ b/Tetris/tetris/track_spec.py @@ -0,0 +1,125 @@ +from enum import Enum, auto +from typing import List, Union, Optional + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import DbUnits, Dir +from .error import LayoutError +from .relz import RelZ + + +# # Track Reference +# +# Integer-pair representing a pointer to a [Layer] and track-index. +# +@dataclass +class TrackRef: + layer: int # Layer Index + track: int # Track Index + + +# # Track Crossing +# +# Located intersection between opposite-direction [Layer]s in [Track]-Space +# +@dataclass +class TrackCross: + # "Primary" [Track] being referred to + track: TrackRef + # Intersecting "secondary" track + cross: TrackRef + + # Create from four [int], representing the two (layer-index, track-index) pairs. + def from_parts(layer1: int, index1: int, layer2: int, index2: int) -> "TrackCross": + return TrackCross( + track=TrackRef(layer1, index1), cross=TrackRef(layer2, index2), + ) + + # Create from a (layer-index, track-index) pair and a [RelZ] + def from_relz(layer: int, track: int, at: int, relz: RelZ) -> "TrackCross": + layer2 = layer + 1 if relz == RelZ.Above else layer - 1 + track = TrackRef(layer, track) + cross = TrackRef(layer=layer2, track=at,) + + return TrackCross(track, cross) + + +class TrackType(Enum): + Gap = auto() + Signal = auto() + Pwr = auto() + Gnd = auto() + + def to_string(self) -> str: + if self == TrackEntry.Pwr: + return "VDD" + if self == TrackEntry.Gnd: + return "VSS" + raise ValueError + + +@dataclass +class TrackEntry: + ttype: TrackType + width: DbUnits + + # # Helper method: create of [TrackEntry] of [TrackType] [TrackType.Gap] + # def gap(width: impl Into) -> Self { + # TrackEntry { + # width: width.into(), + # ttype: TrackType.Gap, + # } + # } + # # Helper method: create of [TrackEntry] of [TrackType] [TrackType.Signal] + # def sig(width: impl Into) -> Self { + # TrackEntry { + # width: width.into(), + # ttype: TrackType.Signal, + + +# An array of layout `Entries`, repeated `nrep` times +@dataclass +class Repeat: + entries: List[TrackEntry] + nrep: int + + +# # Track "Specification" Entry +# +# Either a single entry, or repitition thereof. +# +TrackSpec = Union[TrackEntry, Repeat] + +# def gap(width: impl Into) -> Self { +# Self.Entry(TrackEntry { +# width: width.into(), +# ttype: TrackType.Gap, +# }) +# } +# def sig(width: impl Into) -> Self { +# Self.Entry(TrackEntry { +# width: width.into(), +# ttype: TrackType.Signal, +# }) +# } +# def rail(width: impl Into, rk: RailKind) -> Self { +# Self.Entry(TrackEntry { +# width: width.into(), +# ttype: TrackType.Rail(rk), +# }) +# } +# def pwr(width: impl Into) -> Self { +# Self.Entry(TrackEntry { +# width: width.into(), +# ttype: TrackType.Rail(RailKind.Pwr), +# }) +# } +# def gnd(width: impl Into) -> Self { +# Self.Entry(TrackEntry { +# width: width.into(), +# ttype: TrackType.Rail(RailKind.Gnd), +# }) +# } +# def repeat(e: impl Into>, nrep: int) -> Self { +# Self.Repeat(Repeat(e, nrep)) diff --git a/Tetris/tetris/tracks.py b/Tetris/tetris/tracks.py new file mode 100644 index 0000000..4cb46d5 --- /dev/null +++ b/Tetris/tetris/tracks.py @@ -0,0 +1,253 @@ + +from enum import Enum, auto +from typing import List, Union, Optional + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import DbUnits, Dir +from .error import LayoutError +from .stack import Assign +from .relz import RelZ +# from .instance import Instance + + +@dataclass +class TrackSegmentType: + ... # FIXME! +# enum TrackSegmentType<'lib> { +# Cut { src: 'lib TrackCross }, +# Blockage { src: Ptr }, +# Wire { src: Option<'lib Assign> }, +# Rail(RailKind), + +# # Segments of un-split, single-net wire on a [Track] +@dataclass +class TrackSegment: + # Segment-Type + tp: TrackSegmentType + # Start Location, in [Stack]'s `units` + start: DbUnits + # End/Stop Location, in [Stack]'s `units` + stop: DbUnits + + +@dataclass +enum TrackConflict { + Assign(Assign), + Cut(TrackCross), + Blockage(Ptr), +} +impl std.fmt.Display for TrackConflict { + def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result { + match self { + # Delegate simpler types to [Debug] + TrackConflict.Assign(a) => std.fmt.Debug.fmt(a, f), + TrackConflict.Cut(c) => std.fmt.Debug.fmt(c, f), + # And for more complicated ones, [Display] + TrackConflict.Blockage(i) => std.fmt.Debug.fmt(i, f), + } + } +} +impl From> for TrackConflict { + def from(tp: TrackSegmentType<'_>) -> Self { + match tp { + TrackSegmentType.Cut { src } => TrackConflict.Cut(src.clone()), + TrackSegmentType.Blockage { src } => TrackConflict.Blockage(src.clone()), + _ => unreachable!(), + } + } +} +enum TrackError { + OutOfBounds(DbUnits), + Overlap(DbUnits, DbUnits), + Conflict(TrackConflict, TrackConflict), + CutConflict(TrackConflict, TrackCross), + BlockageConflict(TrackConflict, Ptr), +} +type TrackResult = Result +impl std.fmt.Debug for TrackError { + # Display a [TrackError] + def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result { + match self { + TrackError.OutOfBounds(stop) => write!(f, "Track Out of Bounds: {:}", stop), + TrackError.Overlap(p0, p1) => { + write!(f, "Overlapping Track cuts at: {:}, {:}", p0, p1) + } + TrackError.CutConflict(t0, t1) => { + write!(f, "Conflicting Track-Cuts at: {:}, {:}", t0, t1) + } + TrackError.BlockageConflict(t0, t1) => { + write!( + f, + "Conflicting Instance Blockages: \n * {}\n * {:}\n", + t0, t1 + ) + } + TrackError.Conflict(t0, t1) => { + write!(f, "Conflict Between: \n * {}\n * {:}\n", t0, t1) + } + } + } +} +impl std.fmt.Display for TrackError { + # Display a [TrackError] + def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result { + std.fmt.Debug.fmt(self, f) + } +} +impl std.error.Error for TrackError {} +impl Into for TrackError { + def into(self) -> LayoutError { + LayoutError.Boxed(Box(self)) + } +} + + +@dataclass +class TrackData : + # Track Type (Rail, Signal) + ttype: TrackType + # Track Index + index: int + # Direction + dir: Dir + # Starting-point in off-dir axis + start: DbUnits + # Track width + width: DbUnits + +# # Track +# +# An "instantiated" track, including: +# * Track-long data in a [TrackData], and +# * A set of [TrackSegment]s +@dataclass +class Track: + # Track-long data + data: TrackData + # Set of wire-segments in positional order + segments: List[TrackSegment] + + # Verify a (generally just-created) [Track] is valid + def validate(self) -> "Track" : + if self.data.width < DbUnits(0) : + raise LayoutError("Negative Track Width") + return self + + # Set the net of the track-segment at `at` to `net` + def set_net(self, at: DbUnits, assn: Assign) -> None : + # First find the segment to be modified + seg = None + for s in self.segments.iter_mut() { + if s.start > at { + break + } + if s.start <= at s.stop >= at { + seg = Some(s) + break + } + } + match seg { + None => Err(TrackError.OutOfBounds(at)), + Some(seg) => match seg.tp { + TrackSegmentType.Rail(_) => unreachable!(), + TrackSegmentType.Cut { .. } => Err(TrackError.Conflict( + # Error: trying to assign a net onto a Cut. + TrackConflict.Assign(assn.clone()), + TrackConflict.from(seg.tp.clone()), + )), + TrackSegmentType.Blockage { .. } => { + # FIXME: sort out the desired behaviour here. + # Vias above ZTop instance-pins generally land in this case. + # We could check for their locations Or just it go. + Ok(()) + } + TrackSegmentType.Wire { ref src, .. } => { + # The good case - assignment succeeds. + src.replace(assn) + Ok(()) + + # Insert a cut or blockage corresponding to `blockage`. + def cut_or_block( + self, + start: DbUnits, + stop: DbUnits, + tp: TrackSegmentType, + ) -> None: + # First bounds-check against the end of our segments, which are the end of the cell + if stop > self.segments.last().unwrap().stop : + return Err(TrackError.OutOfBounds(stop)) + + # Find the segment where the blockage starts + segidx = self + .segments + .iter_mut() + .position(|seg| seg.stop > start) + .ok_or(TrackError.OutOfBounds(start)) + .clone() + seg = self.segments[segidx] + # Check for conflicts, and get a copy of our segment-type as we will likely insert a similar segment + tpcopy = match seg.tp { + TrackSegmentType.Blockage { ref src } => { + return Err(TrackError.BlockageConflict( + TrackConflict.from(tp), + src.clone(), + )) + } + TrackSegmentType.Cut { src } => { + return Err(TrackError.CutConflict( + TrackConflict.from(tp), + src.clone(), + )) + } + TrackSegmentType.Wire { .. } => seg.tp.clone(), + TrackSegmentType.Rail(_) => seg.tp.clone(), + } + # Make sure the cut only effects one segment, or fail + if seg.stop < stop { + # FIXME this should really be the *next* segment, borrow checking fight + return Err(TrackError.Overlap(seg.stop, stop)) + } + + # All clear time to cut it. + # In the more-common case in which the cut-end and segment-end *do not* coincide, create and insert a new segment. + to_be_inserted: Vec<(int, TrackSegment)> = Vec() + to_be_inserted.push((segidx + 1, TrackSegment { start, stop, tp })) + if seg.stop != stop { + newseg = TrackSegment { + tp: tpcopy, + start: stop, + stop: seg.stop, + } + to_be_inserted.push((segidx + 2, newseg)) + } + # Update the existing segment (and importantly, drop its mutable borrow) + seg.stop = start + for (idx, seg) in to_be_inserted { + self.segments.insert(idx, seg) + } + Ok(()) + } + # Insert a blockage from `start` to `stop`. + # Fails if the region is not a contiguous wire segment. + # def block(self, start: DbUnits, stop: DbUnits, src: Instance) -> None: + # return self.cut_or_block(start, stop, TrackSegmentType.Blockage (src)) + # } + # Cut from `start` to `stop`. + # Fails if the region is not a contiguous wire segment. + def cut( + self, + start: DbUnits, + stop: DbUnits, + src: TrackCross, + ) -> None : + self.cut_or_block(start, stop, TrackSegmentType.Cut (src)) + } + # Set the stop position for our last [TrackSegment] to `stop` + def stop(self, stop: DbUnits) -> None: + if self.segments.len() == 0 : + raise LayoutError("Error Stopping Track") + + idx = self.segments.len() - 1 + self.segments[idx].stop = stop diff --git a/Tetris/tetris/tracks.rs b/Tetris/tetris/tracks.rs deleted file mode 100644 index 172db9c..0000000 --- a/Tetris/tetris/tracks.rs +++ /dev/null @@ -1,407 +0,0 @@ -// Std-lib imports -use std::fmt::Debug; - -// Crates.io -use serde::{Deserialize, Serialize}; - -// Local imports -use crate::coords::DbUnits; -use crate::instance::Instance; -use crate::raw::{Dir, LayoutError, LayoutResult}; -use crate::stack::{Assign, RelZ}; -use crate::utils::Ptr; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct TrackEntry { - pub ttype: TrackType, - pub width: DbUnits, -} -impl TrackEntry { - /// Helper method: create of [TrackEntry] of [TrackType] [TrackType::Gap] - pub fn gap(width: impl Into) -> Self { - TrackEntry { - width: width.into(), - ttype: TrackType::Gap, - } - } - /// Helper method: create of [TrackEntry] of [TrackType] [TrackType::Signal] - pub fn sig(width: impl Into) -> Self { - TrackEntry { - width: width.into(), - ttype: TrackType::Signal, - } - } -} -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum TrackType { - Gap, - Signal, - Rail(RailKind), -} -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum RailKind { - Pwr, - Gnd, -} -impl RailKind { - pub fn to_string(&self) -> String { - match self { - Self::Pwr => "VDD".into(), - Self::Gnd => "VSS".into(), - } - } -} -/// # Track "Specification" Entry -/// -/// Either a single entry, or repitition thereof. -/// -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum TrackSpec { - Entry(TrackEntry), - Repeat(Repeat), -} -impl TrackSpec { - pub fn gap(width: impl Into) -> Self { - Self::Entry(TrackEntry { - width: width.into(), - ttype: TrackType::Gap, - }) - } - pub fn sig(width: impl Into) -> Self { - Self::Entry(TrackEntry { - width: width.into(), - ttype: TrackType::Signal, - }) - } - pub fn rail(width: impl Into, rk: RailKind) -> Self { - Self::Entry(TrackEntry { - width: width.into(), - ttype: TrackType::Rail(rk), - }) - } - pub fn pwr(width: impl Into) -> Self { - Self::Entry(TrackEntry { - width: width.into(), - ttype: TrackType::Rail(RailKind::Pwr), - }) - } - pub fn gnd(width: impl Into) -> Self { - Self::Entry(TrackEntry { - width: width.into(), - ttype: TrackType::Rail(RailKind::Gnd), - }) - } - pub fn repeat(e: impl Into>, nrep: usize) -> Self { - Self::Repeat(Repeat::new(e, nrep)) - } -} -/// An array of layout `Entries`, repeated `nrep` times -#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq)] -pub struct Repeat { - pub entries: Vec, - pub nrep: usize, -} -impl Repeat { - pub fn new(e: impl Into>, nrep: usize) -> Self { - Self { - entries: e.into(), - nrep, - } - } -} -#[derive(Debug, Clone)] -pub struct TrackData { - /// Track Type (Rail, Signal) - pub ttype: TrackType, - /// Track Index - pub index: usize, - /// Direction - pub dir: Dir, - /// Starting-point in off-dir axis - pub start: DbUnits, - /// Track width - pub width: DbUnits, -} -/// # Track -/// -/// An "instantiated" track, including: -/// * Track-long data in a [TrackData], and -/// * A set of [TrackSegment]s -#[derive(Debug, Clone)] -pub struct Track<'lib> { - /// Track-long data - pub data: TrackData, - /// Set of wire-segments, in positional order - pub segments: Vec>, -} -impl<'lib> Track<'lib> { - /// Verify a (generally just-created) [Track] is valid - pub fn validate(self) -> LayoutResult { - if self.data.width < DbUnits(0) { - return Err(LayoutError::from("Negative Track Width")); - } - Ok(self) - } - /// Set the net of the track-segment at `at` to `net` - pub fn set_net(&mut self, at: DbUnits, assn: &'lib Assign) -> TrackResult<()> { - // First find the segment to be modified - let mut seg = None; - for s in self.segments.iter_mut() { - if s.start > at { - break; - } - if s.start <= at && s.stop >= at { - seg = Some(s); - break; - } - } - match seg { - None => Err(TrackError::OutOfBounds(at)), - Some(seg) => match seg.tp { - TrackSegmentType::Rail(_) => unreachable!(), - TrackSegmentType::Cut { .. } => Err(TrackError::Conflict( - // Error: trying to assign a net onto a Cut. - TrackConflict::Assign(assn.clone()), - TrackConflict::from(seg.tp.clone()), - )), - TrackSegmentType::Blockage { .. } => { - // FIXME: sort out the desired behaviour here. - // Vias above ZTop instance-pins generally land in this case. - // We could check for their locations? Or just let it go. - Ok(()) - } - TrackSegmentType::Wire { ref mut src, .. } => { - // The good case - assignment succeeds. - src.replace(assn); - Ok(()) - } - }, - } - } - /// Insert a cut or blockage corresponding to `blockage`. - pub fn cut_or_block( - &mut self, - start: DbUnits, - stop: DbUnits, - tp: TrackSegmentType<'lib>, - ) -> TrackResult<()> { - // First bounds-check against the end of our segments, which are the end of the cell - if stop > self.segments.last().unwrap().stop { - return Err(TrackError::OutOfBounds(stop)); - } - // Find the segment where the blockage starts - let segidx = self - .segments - .iter_mut() - .position(|seg| seg.stop > start) - .ok_or(TrackError::OutOfBounds(start))? - .clone(); - let seg = &mut self.segments[segidx]; - // Check for conflicts, and get a copy of our segment-type as we will likely insert a similar segment - let tpcopy = match seg.tp { - TrackSegmentType::Blockage { ref src } => { - return Err(TrackError::BlockageConflict( - TrackConflict::from(tp), - src.clone(), - )); - } - TrackSegmentType::Cut { src } => { - return Err(TrackError::CutConflict( - TrackConflict::from(tp), - src.clone(), - )); - } - TrackSegmentType::Wire { .. } => seg.tp.clone(), - TrackSegmentType::Rail(_) => seg.tp.clone(), - }; - // Make sure the cut only effects one segment, or fail - if seg.stop < stop { - // FIXME this should really be the *next* segment, borrow checking fight - return Err(TrackError::Overlap(seg.stop, stop)); - } - - // All clear; time to cut it. - // In the more-common case in which the cut-end and segment-end *do not* coincide, create and insert a new segment. - let mut to_be_inserted: Vec<(usize, TrackSegment)> = Vec::new(); - to_be_inserted.push((segidx + 1, TrackSegment { start, stop, tp })); - if seg.stop != stop { - let newseg = TrackSegment { - tp: tpcopy, - start: stop, - stop: seg.stop, - }; - to_be_inserted.push((segidx + 2, newseg)); - } - // Update the existing segment (and importantly, drop its mutable borrow) - seg.stop = start; - for (idx, seg) in to_be_inserted { - self.segments.insert(idx, seg); - } - Ok(()) - } - /// Insert a blockage from `start` to `stop`. - /// Fails if the region is not a contiguous wire segment. - pub fn block(&mut self, start: DbUnits, stop: DbUnits, src: &Ptr) -> TrackResult<()> { - self.cut_or_block(start, stop, TrackSegmentType::Blockage { src: src.clone() }) - } - /// Cut from `start` to `stop`. - /// Fails if the region is not a contiguous wire segment. - pub fn cut( - &mut self, - start: DbUnits, - stop: DbUnits, - src: &'lib TrackCross, - ) -> TrackResult<()> { - self.cut_or_block(start, stop, TrackSegmentType::Cut { src }) - } - /// Set the stop position for our last [TrackSegment] to `stop` - pub fn stop(&mut self, stop: DbUnits) -> LayoutResult<()> { - if self.segments.len() == 0 { - LayoutError::fail("Error Stopping Track")?; - } - let idx = self.segments.len() - 1; - self.segments[idx].stop = stop; - Ok(()) - } -} -/// # Segments of un-split, single-net wire on a [Track] -#[derive(Debug, Clone)] -pub struct TrackSegment<'lib> { - /// Segment-Type - pub tp: TrackSegmentType<'lib>, - /// Start Location, in [Stack]'s `units` - pub start: DbUnits, - /// End/Stop Location, in [Stack]'s `units` - pub stop: DbUnits, -} -#[derive(Debug, Clone)] -pub enum TrackSegmentType<'lib> { - Cut { src: &'lib TrackCross }, - Blockage { src: Ptr }, - Wire { src: Option<&'lib Assign> }, - Rail(RailKind), -} -/// # Track Reference -/// -/// Integer-pair representing a pointer to a [Layer] and track-index. -/// -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct TrackRef { - /// Layer Index - pub layer: usize, - /// Track Index - pub track: usize, -} -impl TrackRef { - /// Create a new [TrackRef] - pub fn new(layer: usize, track: usize) -> Self { - Self { layer, track } - } -} -/// # Track Crossing -/// -/// Located intersection between opposite-direction [Layer]s in [Track]-Space -/// -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct TrackCross { - /// "Primary" [Track] being referred to - pub track: TrackRef, - /// Intersecting "secondary" track - pub cross: TrackRef, -} -impl TrackCross { - pub fn new(track: TrackRef, cross: TrackRef) -> Self { - Self { track, cross } - } - /// Create from four [usize], representing the two (layer-index, track-index) pairs. - pub fn from_parts(layer1: usize, index1: usize, layer2: usize, index2: usize) -> Self { - Self { - track: TrackRef::new(layer1, index1), - cross: TrackRef::new(layer2, index2), - } - } - /// Create from a (layer-index, track-index) pair and a [RelZ] - pub fn from_relz(layer: usize, track: usize, at: usize, relz: RelZ) -> Self { - let layer2 = match relz { - RelZ::Above => layer + 1, - RelZ::Below => layer - 1, - }; - let track = TrackRef { layer, track }; - let cross = TrackRef { - layer: layer2, - track: at, - }; - Self::new(track, cross) - } -} - -#[derive(Debug, Clone)] -pub enum TrackConflict { - Assign(Assign), - Cut(TrackCross), - Blockage(Ptr), -} -impl std::fmt::Display for TrackConflict { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - // Delegate simpler types to [Debug] - TrackConflict::Assign(a) => std::fmt::Debug::fmt(a, f), - TrackConflict::Cut(c) => std::fmt::Debug::fmt(c, f), - // And for more complicated ones, [Display] - TrackConflict::Blockage(i) => std::fmt::Debug::fmt(i, f), - } - } -} -impl From> for TrackConflict { - fn from(tp: TrackSegmentType<'_>) -> Self { - match tp { - TrackSegmentType::Cut { src } => TrackConflict::Cut(src.clone()), - TrackSegmentType::Blockage { src } => TrackConflict::Blockage(src.clone()), - _ => unreachable!(), - } - } -} -pub enum TrackError { - OutOfBounds(DbUnits), - Overlap(DbUnits, DbUnits), - Conflict(TrackConflict, TrackConflict), - CutConflict(TrackConflict, TrackCross), - BlockageConflict(TrackConflict, Ptr), -} -pub type TrackResult = Result; -impl std::fmt::Debug for TrackError { - /// Display a [TrackError] - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - TrackError::OutOfBounds(stop) => write!(f, "Track Out of Bounds: {:?}", stop), - TrackError::Overlap(p0, p1) => { - write!(f, "Overlapping Track cuts at: {:?}, {:?}", p0, p1) - } - TrackError::CutConflict(t0, t1) => { - write!(f, "Conflicting Track-Cuts at: {:?}, {:?}", t0, t1) - } - TrackError::BlockageConflict(t0, t1) => { - write!( - f, - "Conflicting Instance Blockages: \n * {}\n * {:?}\n", - t0, t1 - ) - } - TrackError::Conflict(t0, t1) => { - write!(f, "Conflict Between: \n * {}\n * {:?}\n", t0, t1) - } - } - } -} -impl std::fmt::Display for TrackError { - /// Display a [TrackError] - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) - } -} -impl std::error::Error for TrackError {} -impl Into for TrackError { - fn into(self) -> LayoutError { - LayoutError::Boxed(Box::new(self)) - } -} From 520b7a5a83902ee1470fc842f4407334ebcf4ad7 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 12 May 2022 08:52:29 -0700 Subject: [PATCH 16/28] WIP, Near Complete Tetris Porting --- Tetris/tetris/__init__.py | 22 +-- Tetris/tetris/abs.rs | 116 ---------------- Tetris/tetris/abstract.py | 113 +++++++++++++++ Tetris/tetris/align.py | 28 ++++ Tetris/tetris/array.py | 35 +++++ Tetris/tetris/array.rs | 94 ------------- Tetris/tetris/bbox.py | 39 ++++++ Tetris/tetris/bbox.rs | 58 -------- Tetris/tetris/bundle.py | 40 ++++++ Tetris/tetris/cell.py | 10 +- Tetris/tetris/conv/layer_period.py | 170 +++++++++++++++++++++++ Tetris/tetris/{ => conv}/tracks.py | 166 +++++++++++----------- Tetris/tetris/coords.py | 6 +- Tetris/tetris/group.py | 28 ++++ Tetris/tetris/group.rs | 51 ------- Tetris/tetris/instance.py | 66 +++++---- Tetris/tetris/instantiable.py | 10 ++ Tetris/tetris/interface.rs | 36 ----- Tetris/tetris/layer_period.py | 175 ------------------------ Tetris/tetris/layout.py | 16 +-- Tetris/tetris/net_handle.py | 10 +- Tetris/tetris/placement.py | 75 ++++++++++ Tetris/tetris/placement.rs | 212 ----------------------------- Tetris/tetris/separation.py | 50 +++++++ Tetris/tetris/side.py | 33 +++++ Tetris/tetris/stack.py | 8 +- Tetris/tetris/units.py | 7 + 27 files changed, 787 insertions(+), 887 deletions(-) delete mode 100644 Tetris/tetris/abs.rs create mode 100644 Tetris/tetris/abstract.py create mode 100644 Tetris/tetris/align.py create mode 100644 Tetris/tetris/array.py delete mode 100644 Tetris/tetris/array.rs create mode 100644 Tetris/tetris/bbox.py delete mode 100644 Tetris/tetris/bbox.rs create mode 100644 Tetris/tetris/bundle.py create mode 100644 Tetris/tetris/conv/layer_period.py rename Tetris/tetris/{ => conv}/tracks.py (69%) create mode 100644 Tetris/tetris/group.py delete mode 100644 Tetris/tetris/group.rs create mode 100644 Tetris/tetris/instantiable.py delete mode 100644 Tetris/tetris/interface.rs delete mode 100644 Tetris/tetris/layer_period.py create mode 100644 Tetris/tetris/placement.py delete mode 100644 Tetris/tetris/placement.rs create mode 100644 Tetris/tetris/separation.py create mode 100644 Tetris/tetris/side.py create mode 100644 Tetris/tetris/units.py diff --git a/Tetris/tetris/__init__.py b/Tetris/tetris/__init__.py index f703a2d..19dbc4d 100644 --- a/Tetris/tetris/__init__.py +++ b/Tetris/tetris/__init__.py @@ -3,23 +3,23 @@ from .coords import * from .outline import * -from .relz import * +from .relz import * from .stack import * from .track_spec import * - +from .abstract import * from .layout import * from .cell import * from .library import * +from .bundle import * +from .bbox import * +from .instance import * +from .placement import * +from .array import * +from .group import * # from .layer_period import * -# from .abs import * -# from .array import * -# from .bbox import * -# from .conv import * -# from .group import * -# from .instance import * -# from .interface import * -# from .placement import * -# from .placer import * # from .tracks import * + +# from .placer import * # from .validate import * +# from .conv import * diff --git a/Tetris/tetris/abs.rs b/Tetris/tetris/abs.rs deleted file mode 100644 index 3802ef5..0000000 --- a/Tetris/tetris/abs.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! -//! # Abstract Layout Module -//! -//! [Abstract] layouts describe a [Cell]'s outline and physical interface, without exposing implementation details. -//! [Cell]-[Abstract]s primarily comprise their outlines and pins. -//! Outlines follow the same "Tetris-Shapes" as `layout21::tetris` layout cells, including the requirements for a uniform z-axis. -//! Internal layers are "fully blocked", in that parent layouts may not route through them. -//! In legacy layout systems this would be akin to including blockages of the same shape as [Outline] on each layer. -//! -//! Sadly the english-spelled name "abstract" is reserved as a potential -//! [future Rust keyword](https://doc.rust-lang.org/reference/keywords.html#reserved-keywords), -//! and is hence avoided as an identifier throughout Layout21. -//! - -// Crates.io -use serde::{Deserialize, Serialize}; - -// Local imports -use crate::outline; -use crate::stack::RelZ; - -/// Abstract-Layout -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Abstract { - /// Cell Name - pub name: String, - /// Outline in "Tetris-Shapes" - pub outline: outline::Outline, - /// Number of Metal Layers Used - pub metals: usize, - /// Ports - pub ports: Vec, -} -impl Abstract { - /// Create a new abstract layout. No ports are initially defined. - pub fn new(name: impl Into, metals: usize, outline: outline::Outline) -> Self { - Self { - name: name.into(), - outline, - metals, - ports: Vec::new(), - } - } - /// Retrieve a reference to a port by name. - /// Returns `None` if no port with that name exists. - pub fn port(&self, name: &str) -> Option<&Port> { - for port in &self.ports { - if port.name == name { - return Some(port); - } - } - None - } -} -/// Abstract-Layout Port -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Port { - /// Port/ Signal Name - pub name: String, - /// Physical Info - pub kind: PortKind, -} -/// Abstract-Layout Port Inner Detail -/// -/// All location and "geometric" information per Port is stored here, -/// among a few enumerated variants. -/// -/// Ports may either connect on x/y edges, or on the top (in the z-axis) layer. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PortKind { - /// Ports which connect on x/y outline edges - Edge { - layer: usize, - track: usize, - side: Side, - }, - /// Ports accessible from bot top *and* top-layer edges - /// Note their `layer` field is implicitly defined as the cell's `metals`. - ZTopEdge { - /// Track Index - track: usize, - /// Side - side: Side, - /// Location into which the pin extends inward - into: (usize, RelZ), - }, - /// Ports which are internal to the cell outline, - /// but connect from above in the z-stack. - /// These can be assigned at several locations across their track, - /// and are presumed to be internally-connected between such locations. - ZTopInner { - /// Locations - locs: Vec, - }, -} -/// A location (track intersection) on our top z-axis layer -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TopLoc { - /// Track Index - track: usize, - /// Intersecting Track Index - at: usize, - /// Whether `at` refers to the track-indices above or below - relz: RelZ, -} -/// # Port Side Enumeration -/// -/// Note there are only two such sides: the "origin-side" [BottomOrLeft] and the "width-side" [TopOrRight]. -/// Each [Layer]'s orientation ([Dir]) dictates between bottom/left and top/right. -/// Also note the requirements on [Outline] shapes ensure each track has a unique left/right or top/bottom pair of edges. -/// -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum Side { - BottomOrLeft, - TopOrRight, -} diff --git a/Tetris/tetris/abstract.py b/Tetris/tetris/abstract.py new file mode 100644 index 0000000..9cb9a8a --- /dev/null +++ b/Tetris/tetris/abstract.py @@ -0,0 +1,113 @@ +# +# # Abstract Layout Module +# +# [Abstract] layouts describe a [Cell]'s outline and physical interface, without exposing implementation details. +# [Cell]-[Abstract]s primarily comprise their outlines and pins. +# Outlines follow the same "Tetris-Shapes" as `layout21.tetris` layout cells, including the requirements for a uniform z-axis. +# Internal layers are "fully blocked", in that parent layouts may not route through them. +# In legacy layout systems this would be akin to including blockages of the same shape as [Outline] on each layer. +# +# Sadly the english-spelled name "abstract" is reserved as a potential +# [future Rust keyword](https:#doc.rust-lang.org/reference/keywords.html#reserved-keywords), +# and is hence avoided as an identifier throughout Layout21. +# + +from enum import Enum, auto +from typing import List, Union, Optional, Tuple + +from pydantic.dataclasses import dataclass + +# Local imports +from .outline import Outline +from .relz import RelZ + +# A location (track intersection) on our top z-axis layer +@dataclass +class TopLoc: + # Track Index + track: int + # Intersecting Track Index + at: int + # Whether `at` refers to the track-indices above or below + relz: RelZ + + +# # Port Side Enumeration +# +# Note there are only two such sides: the "origin-side" [BottomOrLeft] and the "width-side" [TopOrRight]. +# Each [Layer]'s orientation ([Dir]) dictates between bottom/left and top/right. +# Also note the requirements on [Outline] shapes ensure each track has a unique left/right or top/bottom pair of edges. +# +@dataclass +class Side(Enum): + BottomOrLeft = auto() + TopOrRight = auto() + + +# Ports which connect on x/y outline edges +@dataclass +class Edge: + layer: int + track: int + side: Side + + +# Ports accessible from bot top *and* top-layer edges +# Note their `layer` field is implicitly defined as the cell's `metals`. +@dataclass +class ZTopEdge: + # Track Index + track: int + # Side + side: Side + # Location into which the pin extends inward + into: Tuple[int, RelZ] + + +# Ports which are internal to the cell outline, +# but connect from above in the z-stack. +# These can be assigned at several locations across their track, +# and are presumed to be internally-connected between such locations. +@dataclass +class ZTopInner: + # Locations + locs: List[TopLoc] + + +# Abstract-Layout Port Inner Detail +# +# All location and "geometric" information per Port is stored here, +# among a few enumerated variants. +# +# Ports may either connect on x/y edges, or on the top (in the z-axis) layer. +PortKind = Union[Edge, ZTopInner, ZTopEdge] + + +# Abstract-Layout Port +@dataclass +class Port: + # Port/ Signal Name + name: str + # Physical Info + kind: PortKind + + +# Abstract-Layout +@dataclass +class Abstract: + # Cell Name + name: str + # Outline in "Tetris-Shapes" + outline: Outline + # Number of Metal Layers Used + metals: int + # Ports + ports: List[Port] + + # Retrieve a reference to a port by name. + # Returns `None` if no port with that name exists. + def port(self, name: str) -> Optional[Port]: + for port in self.ports: + if port.name == name: + return port + return None diff --git a/Tetris/tetris/align.py b/Tetris/tetris/align.py new file mode 100644 index 0000000..2a3a31b --- /dev/null +++ b/Tetris/tetris/align.py @@ -0,0 +1,28 @@ +from typing import Tuple, Union + +from pydantic.dataclasses import dataclass + +from .side import Side + + +@dataclass +class AlignSide: + # Side-to-side alignment + side: Side + + +@dataclass +class AlignCenter: + # Center-aligned + ... # No data + + +@dataclass +class AlignPorts: + # Port-to-port alignment + ports: Tuple[str, str] + + +# Alignment types +Align = Union[AlignSide, AlignCenter, AlignPorts] + diff --git a/Tetris/tetris/array.py b/Tetris/tetris/array.py new file mode 100644 index 0000000..8aa7209 --- /dev/null +++ b/Tetris/tetris/array.py @@ -0,0 +1,35 @@ +# +# # Layout Arrays +# +# Uniformly-spaced repetitions of [Arrayable] elements. +# + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import PrimPitches, Xy +from .separation import Separation + +# from .instantiable import Instantiable + + +@dataclass +class Array: + """ # Array + A Uniform-Spaced Array of Identical [`Instantiable`] Elements """ + + # Array Name + name: str + # Unit to be Arrayed + unit: "Instantiable" + # Number of elements + count: int + # Separation between elements + # FIXME: whether to include the size of the element or not + sep: Separation + + # Size of the Array's rectangular `boundbox` i.e. the zero-origin `boundbox` of its `cell`. + def boundbox_size(self) -> Xy[PrimPitches]: + _unit = self.unit.boundbox_size() + raise NotImplementedError # FIXME! + diff --git a/Tetris/tetris/array.rs b/Tetris/tetris/array.rs deleted file mode 100644 index d073980..0000000 --- a/Tetris/tetris/array.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! -//! # Layout Arrays -//! -//! Uniformly-spaced repetitions of [Arrayable] elements. -//! - -// Local imports -use crate::{ - bbox::{BoundBox, HasBoundBox}, - cell::Cell, - coords::{PrimPitches, Xy}, - group::Group, - placement::{Place, Separation}, - raw::{LayoutError, LayoutResult}, - utils::Ptr, -}; - -/// Uniform-Spaced Array of Identical [Placeable] Elements -#[derive(Debug, Clone)] -pub struct Array { - /// Array Name - pub name: String, - /// Unit to be Arrayed - pub unit: Arrayable, - /// Number of elements - pub count: usize, - /// Separation between elements - /// FIXME: whether to include the size of the element or not - pub sep: Separation, -} -impl Array { - /// Size of the Array's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. - pub fn boundbox_size(&self) -> LayoutResult> { - let _unit = self.unit.boundbox_size()?; - todo!() // FIXME: do some math on separation, size - } -} -/// Enumeration of types that can be Arrayed -#[derive(Debug, Clone)] -pub enum Arrayable { - /// Instance of a Cell - Instance(Ptr), - /// Uniform array of placeable elements - Array(Ptr), - /// Group of other placeable elements - Group(Ptr), -} -impl Arrayable { - pub fn boundbox_size(&self) -> LayoutResult> { - match self { - Arrayable::Instance(ref p) => p.read()?.boundbox_size(), - Arrayable::Array(ref p) => p.read()?.boundbox_size(), - Arrayable::Group(ref p) => p.read()?.boundbox_size(), - } - } -} - -/// Located Instance of an Array -#[derive(Debug, Clone)] -pub struct ArrayInstance { - /// Array-Instance Name - pub name: String, - /// Array Definition - pub array: Ptr, - /// Location of first element - pub loc: Place>, - /// Vertical reflection - pub reflect_vert: bool, - /// Horizontal reflection - pub reflect_horiz: bool, -} - -impl HasBoundBox for ArrayInstance { - type Units = PrimPitches; - type Error = LayoutError; - /// Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. - /// Instance location must be resolved to absolute coordinates, or this method will fail. - fn boundbox(&self) -> LayoutResult> { - // FIXME: share most or all of this with [Instance] - - let loc = self.loc.abs()?; - let array = self.array.read()?; - let outline = array.boundbox_size()?; - let (x0, x1) = match self.reflect_horiz { - false => (loc.x, loc.x + outline.x), - true => (loc.x - outline.x, loc.x), - }; - let (y0, y1) = match self.reflect_vert { - false => (loc.y, loc.y + outline.y), - true => (loc.y - outline.y, loc.y), - }; - Ok(BoundBox::new(Xy::new(x0, y0), Xy::new(x1, y1))) - } -} diff --git a/Tetris/tetris/bbox.py b/Tetris/tetris/bbox.py new file mode 100644 index 0000000..3657044 --- /dev/null +++ b/Tetris/tetris/bbox.py @@ -0,0 +1,39 @@ +# +# # Rectangular Bounding Boxes +# + +from typing import TypeVar, Generic, Tuple + +from pydantic.generics import GenericModel + +# Local imports +from .coords import Xy +from .side import Side + + +T = TypeVar("T") + + +# # Bounding Rectangular Box +class BoundBox(GenericModel, Generic[T]): + p0: Xy[T] + p1: Xy[T] + + # Retrieve our coordinate at [Side] `side`. + def side(self, side: Side) -> T: + if side == Side.Left: + return (self.p0.x,) + if side == Side.Right: + return (self.p1.x,) + if side == Side.Bottom: + return (self.p0.y,) + if side == Side.Top: + return (self.p1.y,) + raise ValueError + + # Create a new [BoundBox] from potentially unordered pairs of x and y coordinates. + def from_xy(xs: Tuple[T, T], ys: Tuple[T, T]) -> "BoundBox": + (x0, x1) = (xs[0], xs[1]) if xs[0] < xs[1] else (xs[1], xs[0]) + (y0, y1) = (ys[0], ys[1]) if ys[0] < ys[1] else (ys[1], ys[0]) + return BoundBox(Xy(x0, y0), Xy(x1, y1)) + diff --git a/Tetris/tetris/bbox.rs b/Tetris/tetris/bbox.rs deleted file mode 100644 index 6150c50..0000000 --- a/Tetris/tetris/bbox.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! -//! # Rectangular Bounding Boxes -//! - -// Crates.io -use serde::{Deserialize, Serialize}; - -// Local imports -use crate::coords::{HasUnits, Xy}; -use crate::placement::Side; - -/// # Bounding Rectangular Box -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] -pub struct BoundBox { - pub p0: Xy, - pub p1: Xy, -} -impl BoundBox { - /// Create a new [BoundBox] from two [Xy] points. - pub fn new(p0: Xy, p1: Xy) -> Self { - Self { p0, p1 } - } - /// Retrieve our coordinate at [Side] `side`. - pub fn side(&self, side: Side) -> T { - match side { - Side::Left => self.p0.x, - Side::Right => self.p1.x, - Side::Bottom => self.p0.y, - Side::Top => self.p1.y, - } - } -} -impl BoundBox { - /// Create a new [BoundBox] from potentially unordered pairs of x and y coordinates. - pub fn from_xy(xs: (T, T), ys: (T, T)) -> Self { - let (x0, x1) = if xs.0 < xs.1 { - (xs.0, xs.1) - } else { - (xs.1, xs.0) - }; - let (y0, y1) = if ys.0 < ys.1 { - (ys.0, ys.1) - } else { - (ys.1, ys.0) - }; - Self::new(Xy::new(x0, y0), Xy::new(x1, y1)) - } -} - -/// Trait for types that can be converted to a [BoundBox]. -/// -/// Includes a single method `boundbox` which returns a [BoundBox] of the type. -pub trait HasBoundBox { - type Units: HasUnits; - type Error; - /// Get a [BoundBox] of the type. - fn boundbox(&self) -> Result, Self::Error>; -} diff --git a/Tetris/tetris/bundle.py b/Tetris/tetris/bundle.py new file mode 100644 index 0000000..0862236 --- /dev/null +++ b/Tetris/tetris/bundle.py @@ -0,0 +1,40 @@ +# +# # Interfaces Module +# +# Describing Cells in terms of their IO Interfaces +# + +from typing import List +from enum import Enum, auto + +from pydantic.dataclasses import dataclass + + +class PortKind(Enum): + # Flat Scalar Port, e.g. `clk` + Scalar = auto() + # Array-Based Port, e.g. `data[31:0]` + Array = auto() + # Instance of a Hierarchical Bundle + Bundle = auto() + + +# # Port +# +# Logical port, as in a netlist or HDL description. +# Includes scalar, vector (bus), and bundle-valued ports. +# Does not include physical/ geometric information. +# +@dataclass +class Port: + # Port Name + name: str + # Port Type Content + kind: PortKind + + +@dataclass +class Bundle: + name: str + ports: List[Port] + diff --git a/Tetris/tetris/cell.py b/Tetris/tetris/cell.py index 506ba30..6f97509 100644 --- a/Tetris/tetris/cell.py +++ b/Tetris/tetris/cell.py @@ -17,8 +17,8 @@ from .layout import Layout from .outline import Outline from .error import LayoutError -# from .bundle import Bundle -# from .abstract import Abstract +from .bundle import Bundle +from .abstract import Abstract # "Pointer" to a raw (lib, cell) combination. # Wraps with basic [Outline] and `metals` information to enable bounded placement. @@ -39,7 +39,7 @@ class RawLayoutPtr: # # Cell View Enumeration # All of the ways in which a Cell is represented CellView = Union[ - "Bundle", "Abstract", Layout, RawLayoutPtr, + Bundle, Abstract, Layout, RawLayoutPtr, ] # Collection of the Views describing a Cell @@ -48,9 +48,9 @@ class Cell: # Cell Name name: str # Interface - interface: Optional["Bundle"] = None + interface: Optional[Bundle] = None # Layout Abstract - abs: Optional["Abstract"] = None + abs: Optional[Abstract] = None # Layout Implementation layout: Optional[Layout] = None diff --git a/Tetris/tetris/conv/layer_period.py b/Tetris/tetris/conv/layer_period.py new file mode 100644 index 0000000..be1c443 --- /dev/null +++ b/Tetris/tetris/conv/layer_period.py @@ -0,0 +1,170 @@ + +from typing import List + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import DbUnits +from .track_spec import TrackCross, TrackRef, TrackSpec, TrackType +from .tracks import Track, TrackData + + + + +@dataclass +class LayerPeriodData : + signals: List[TrackData] + rails: List[TrackData] + + + # Convert this [Layer]'s track-info into a [LayerPeriodData] + def to_layer_period_data(self) -> "LayerPeriodData": + period = LayerPeriodData.default() + cursor = self.offset + for e in self.entries() : + d = e.width + match e.ttype : + TrackType.Gap => (), + TrackType.Rail(_railkind) => : + period.rails.push(TrackData : + ttype: e.ttype, + index: period.rails.len(), + dir: self.dir, + start: cursor, + width: d, + ) + + TrackType.Signal => : + period.signals.push(TrackData : + ttype: e.ttype, + index: period.signals.len(), + dir: self.dir, + start: cursor, + width: d, + ) + + + cursor += d + + Ok(period) + + # Convert this [Layer]'s track-info into a [LayerPeriod] + def to_layer_period( + self, + index: int, + stop: DbUnits, + ) -> LayoutResult : + stop = stop.into() + period = LayerPeriod.default() + period.index = index + cursor = self.offset + (self.pitch() * index) + entries = self.entries() + iterator: Box> = + if self.flip == FlipMode.EveryOther index % 2 == 1 : + Box(entries.iter().rev()) + else : + Box(entries.iter()) + + for e in iterator : + d = e.width + match e.ttype : + TrackType.Gap => (), + TrackType.Rail(railkind) => : + period.rails.push( + Track : + data: TrackData : + ttype: e.ttype, + index: period.rails.len(), + dir: self.dir, + start: cursor, + width: d, + , + segments: vec![TrackSegment : + tp: TrackSegmentType.Rail(railkind), + start: 0.into(), + stop, + ], + + .validate(), + ) + + TrackType.Signal => : + period.signals.push( + Track : + data: TrackData : + ttype: e.ttype, + index: period.signals.len(), + dir: self.dir, + start: cursor, + width: d, + , + segments: vec![TrackSegment : + tp: TrackSegmentType.Wire : src: None , + start: 0.into(), + stop, + ], + + .validate(), + ) + + + cursor += d + + Ok(period) + + +# Transformed single period of [Track]s on a [Layer] +# Splits track-info between signals and rails. +# Stores each as a [Track] struct, which moves to a (start, width) size-format, +# and includes a vector of track-segments for cutting and assigning nets. +@dataclass +class LayerPeriod : + index: int + signals: List[Track] + rails: List[Track] + + + # Shift the period by `dist` in its periodic direction + def offset(self, dist: DbUnits) -> None : + for t in self.rails : + t.data.start += dist + + for t in self.signals : + t.data.start += dist + + + + # Set the stop position for all [Track]s to `stop` + def stop(self, stop: DbUnits) -> None : + for t in self.rails : + t.stop(stop) + + for t in self.signals : + t.stop(stop) + + + + # Cut all [Track]s from `start` to `stop`, + def cut( + self, + start: DbUnits, + stop: DbUnits, + src: TrackCross, + ) -> None : + for t in self.rails : + t.cut(start, stop, src) + + for t in self.signals : + t.cut(start, stop, src) + + + + # Block all [Track]s from `start` to `stop`, + def block(self, start: DbUnits, stop: DbUnits, src: "Instance") -> None : + for t in self.rails : + t.block(start, stop, src) + + for t in self.signals : + t.block(start, stop, src) + + \ No newline at end of file diff --git a/Tetris/tetris/tracks.py b/Tetris/tetris/conv/tracks.py similarity index 69% rename from Tetris/tetris/tracks.py rename to Tetris/tetris/conv/tracks.py index 4cb46d5..37d80d9 100644 --- a/Tetris/tetris/tracks.py +++ b/Tetris/tetris/conv/tracks.py @@ -1,6 +1,6 @@ from enum import Enum, auto -from typing import List, Union, Optional +from typing import List, Union, Optionalal from pydantic.dataclasses import dataclass @@ -15,10 +15,10 @@ @dataclass class TrackSegmentType: ... # FIXME! -# enum TrackSegmentType<'lib> { -# Cut { src: 'lib TrackCross }, -# Blockage { src: Ptr }, -# Wire { src: Option<'lib Assign> }, +# enum TrackSegmentType<'lib> : +# Cut : src: 'lib TrackCross , +# Blockage : src: Ptr , +# Wire : src: Optional<'lib Assign> , # Rail(RailKind), # # Segments of un-split, single-net wire on a [Track] @@ -33,75 +33,75 @@ class TrackSegment: @dataclass -enum TrackConflict { +enum TrackConflict : Assign(Assign), Cut(TrackCross), Blockage(Ptr), -} -impl std.fmt.Display for TrackConflict { - def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result { - match self { + + + def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result : + match self : # Delegate simpler types to [Debug] TrackConflict.Assign(a) => std.fmt.Debug.fmt(a, f), TrackConflict.Cut(c) => std.fmt.Debug.fmt(c, f), # And for more complicated ones, [Display] TrackConflict.Blockage(i) => std.fmt.Debug.fmt(i, f), - } - } -} -impl From> for TrackConflict { - def from(tp: TrackSegmentType<'_>) -> Self { - match tp { - TrackSegmentType.Cut { src } => TrackConflict.Cut(src.clone()), - TrackSegmentType.Blockage { src } => TrackConflict.Blockage(src.clone()), + + + + + def from(tp: TrackSegmentType<'_>) -> Self : + match tp : + TrackSegmentType.Cut : src => TrackConflict.Cut(src.clone()), + TrackSegmentType.Blockage : src => TrackConflict.Blockage(src.clone()), _ => unreachable!(), - } - } -} -enum TrackError { + + + +enum TrackError : OutOfBounds(DbUnits), Overlap(DbUnits, DbUnits), Conflict(TrackConflict, TrackConflict), CutConflict(TrackConflict, TrackCross), BlockageConflict(TrackConflict, Ptr), -} + type TrackResult = Result -impl std.fmt.Debug for TrackError { + # Display a [TrackError] - def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result { - match self { - TrackError.OutOfBounds(stop) => write!(f, "Track Out of Bounds: {:}", stop), - TrackError.Overlap(p0, p1) => { - write!(f, "Overlapping Track cuts at: {:}, {:}", p0, p1) - } - TrackError.CutConflict(t0, t1) => { - write!(f, "Conflicting Track-Cuts at: {:}, {:}", t0, t1) - } - TrackError.BlockageConflict(t0, t1) => { + def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result : + match self : + TrackError.OutOfBounds(stop) => write!(f, "Track Out of Bounds: ::", stop), + TrackError.Overlap(p0, p1) => : + write!(f, "Overlapping Track cuts at: ::, ::", p0, p1) + + TrackError.CutConflict(t0, t1) => : + write!(f, "Conflicting Track-Cuts at: ::, ::", t0, t1) + + TrackError.BlockageConflict(t0, t1) => : write!( f, - "Conflicting Instance Blockages: \n * {}\n * {:}\n", + "Conflicting Instance Blockages: \n * :\n * ::\n", t0, t1 ) - } - TrackError.Conflict(t0, t1) => { - write!(f, "Conflict Between: \n * {}\n * {:}\n", t0, t1) - } - } - } -} -impl std.fmt.Display for TrackError { + + TrackError.Conflict(t0, t1) => : + write!(f, "Conflict Between: \n * :\n * ::\n", t0, t1) + + + + + # Display a [TrackError] - def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result { + def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result : std.fmt.Debug.fmt(self, f) - } -} -impl std.error.Error for TrackError {} -impl Into for TrackError { - def into(self) -> LayoutError { + + + + + def into(self) -> LayoutError : LayoutError.Boxed(Box(self)) - } -} + + @dataclass @@ -139,31 +139,31 @@ def validate(self) -> "Track" : def set_net(self, at: DbUnits, assn: Assign) -> None : # First find the segment to be modified seg = None - for s in self.segments.iter_mut() { - if s.start > at { + for s in self.segments.iter_mut() : + if s.start > at : break - } - if s.start <= at s.stop >= at { + + if s.start <= at s.stop >= at : seg = Some(s) break - } - } - match seg { + + + match seg : None => Err(TrackError.OutOfBounds(at)), - Some(seg) => match seg.tp { + Some(seg) => match seg.tp : TrackSegmentType.Rail(_) => unreachable!(), - TrackSegmentType.Cut { .. } => Err(TrackError.Conflict( + TrackSegmentType.Cut : .. => Err(TrackError.Conflict( # Error: trying to assign a net onto a Cut. TrackConflict.Assign(assn.clone()), TrackConflict.from(seg.tp.clone()), )), - TrackSegmentType.Blockage { .. } => { + TrackSegmentType.Blockage : .. => : # FIXME: sort out the desired behaviour here. # Vias above ZTop instance-pins generally land in this case. # We could check for their locations Or just it go. Ok(()) - } - TrackSegmentType.Wire { ref src, .. } => { + + TrackSegmentType.Wire : ref src, .. => : # The good case - assignment succeeds. src.replace(assn) Ok(()) @@ -188,52 +188,52 @@ def cut_or_block( .clone() seg = self.segments[segidx] # Check for conflicts, and get a copy of our segment-type as we will likely insert a similar segment - tpcopy = match seg.tp { - TrackSegmentType.Blockage { ref src } => { + tpcopy = match seg.tp : + TrackSegmentType.Blockage : ref src => : return Err(TrackError.BlockageConflict( TrackConflict.from(tp), src.clone(), )) - } - TrackSegmentType.Cut { src } => { + + TrackSegmentType.Cut : src => : return Err(TrackError.CutConflict( TrackConflict.from(tp), src.clone(), )) - } - TrackSegmentType.Wire { .. } => seg.tp.clone(), + + TrackSegmentType.Wire : .. => seg.tp.clone(), TrackSegmentType.Rail(_) => seg.tp.clone(), - } + # Make sure the cut only effects one segment, or fail - if seg.stop < stop { + if seg.stop < stop : # FIXME this should really be the *next* segment, borrow checking fight return Err(TrackError.Overlap(seg.stop, stop)) - } + # All clear time to cut it. # In the more-common case in which the cut-end and segment-end *do not* coincide, create and insert a new segment. - to_be_inserted: Vec<(int, TrackSegment)> = Vec() - to_be_inserted.push((segidx + 1, TrackSegment { start, stop, tp })) - if seg.stop != stop { - newseg = TrackSegment { + to_be_inserted: List<(int, TrackSegment)> = List() + to_be_inserted.push((segidx + 1, TrackSegment : start, stop, tp )) + if seg.stop != stop : + newseg = TrackSegment : tp: tpcopy, start: stop, stop: seg.stop, - } + to_be_inserted.push((segidx + 2, newseg)) - } + # Update the existing segment (and importantly, drop its mutable borrow) seg.stop = start - for (idx, seg) in to_be_inserted { + for (idx, seg) in to_be_inserted : self.segments.insert(idx, seg) - } + Ok(()) - } + # Insert a blockage from `start` to `stop`. # Fails if the region is not a contiguous wire segment. # def block(self, start: DbUnits, stop: DbUnits, src: Instance) -> None: # return self.cut_or_block(start, stop, TrackSegmentType.Blockage (src)) - # } + # # Cut from `start` to `stop`. # Fails if the region is not a contiguous wire segment. def cut( @@ -243,7 +243,7 @@ def cut( src: TrackCross, ) -> None : self.cut_or_block(start, stop, TrackSegmentType.Cut (src)) - } + # Set the stop position for our last [TrackSegment] to `stop` def stop(self, stop: DbUnits) -> None: if self.segments.len() == 0 : diff --git a/Tetris/tetris/coords.py b/Tetris/tetris/coords.py index 1b692a2..ed0cd81 100644 --- a/Tetris/tetris/coords.py +++ b/Tetris/tetris/coords.py @@ -8,8 +8,9 @@ from pydantic.generics import GenericModel from pydantic.dataclasses import dataclass -# Local Imports -from .index import Index +# Local Imports +from .index import Index + class Dir(Enum): """ Enumerated 2-D Directions """ @@ -156,6 +157,7 @@ def negate(self) -> "PrimPitches": # } +@dataclass class LayerPitches: """ Distance in Pitches on a Particular Layer """ diff --git a/Tetris/tetris/group.py b/Tetris/tetris/group.py new file mode 100644 index 0000000..4df8f05 --- /dev/null +++ b/Tetris/tetris/group.py @@ -0,0 +1,28 @@ +# +# # Layout Element Groups +# +# The primary [Group] type is a set of named, located elements +# which can be placed and moved together. +# + +from typing import List + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import PrimPitches, Xy +from .instance import Instance + + +# Named group of placeable elements +@dataclass +class Group: + # Group Name + name: str + # Constituent Elements + elements: List[Instance] + + # Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. + def boundbox_size(self) -> Xy[PrimPitches]: + raise NotImplementedError + diff --git a/Tetris/tetris/group.rs b/Tetris/tetris/group.rs deleted file mode 100644 index 6d56d8d..0000000 --- a/Tetris/tetris/group.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! -//! # Layout Element Groups -//! -//! The primary [Group] type is a set of named, located elements -//! which can be placed and moved together. -//! - -// Local imports -use crate::{ - array::Array, - cell::Cell, - coords::{PrimPitches, Xy}, - placement::Place, - raw::LayoutResult, - utils::Ptr, -}; - -/// Named group of placeable elements -#[derive(Debug, Clone)] -pub struct Group { - /// Group Name - name: String, - /// Constituent Elements - elements: Vec, -} -impl Group { - /// Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. - pub fn boundbox_size(&self) -> LayoutResult> { - todo!() - } -} -/// Enumeration of types that can be Grouped -#[derive(Debug, Clone)] -pub enum Groupable { - /// Instance of a Cell - Instance(Ptr), - /// Uniform array of placeable elements - Array(Ptr), - /// Group of other placeable elements - Group(Ptr), -} -/// Placed Instance of a [Group] -#[derive(Debug, Clone)] -pub struct GroupInstance { - /// Group-Instance Name - pub name: String, - /// Group Definition - pub group: Ptr, - /// Location - pub loc: Place>, -} diff --git a/Tetris/tetris/instance.py b/Tetris/tetris/instance.py index 872aa8e..1b08074 100644 --- a/Tetris/tetris/instance.py +++ b/Tetris/tetris/instance.py @@ -8,56 +8,76 @@ # Local imports from .coords import PrimPitches, Xy, Dir -from .error import LayoutError +from .bbox import BoundBox + +# from .placement import Place +# from .instantiable import Instantiable + + +@dataclass +class Reflection: + """ Reflection-State of an instance """ + + # Horizontal Reflection State + horiz: bool + # Vertical Reflection State + vert: bool + + def reflected(self, dir_: Dir) -> bool: + """ Boolean indication of whether reflected in direction `dir`. """ + if dir_ == Dir.Horiz: + return self.horiz + if dir_ == Dir.Vert: + return self.vert + raise ValueError + + def __getitem__(self, dir_: Dir) -> bool: + """ Square bracket access. Boolean indication of whether reflected in direction `dir`. """ + return self.reflected(dir_) -# from .cell import Cell -# from .place import Place -# Instance of another Cell @dataclass class Instance: + """ Instance of another Cell, Group, or Array""" + # Instance Name inst_name: str - # Cell Definition Reference - cell: "Cell" + + # Target `Cell`, `Group`, `Array`, or other `Instantiable` + of: "Instantiable" + # Location of the Instance origin # This origin-position holds regardless of either `reflect` field. # If specified in absolute coordinates location-units are [PrimPitches]. - loc: "Place[Xy[PrimPitches]]" - # Horizontal Reflection - reflect_horiz: bool - # Vertical Reflection - reflect_vert: bool + loc: "Place" + + # Reflection + reflect: Reflection # Boolean indication of whether this Instance is reflected in direction `dir` def reflected(self, dir_: Dir) -> bool: - if dir_ == Dir.Horiz: - return self.reflect_horiz - if dir_ == Dir.Vert: - return self.reflect_vert - raise ValueError + return self.reflect.reflected(dir_) # Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. def boundbox_size(self) -> "Xy[PrimPitches]": - return self.cell.boundbox_size() + return self.of.boundbox_size() def __repr__(self): - return f"Instance(name=:self.inst_name, cell=:self.cell.name, loc=:self.loc)" + return f"Instance(name=:self.inst_name, cell=:self.of.name, loc=:self.loc)" # Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. # Instance location must be resolved to absolute coordinates, or this method will fail. - def boundbox(self) -> "BoundBox[PrimPitches]": - from .bbox import BoundBox + def boundbox(self) -> BoundBox[PrimPitches]: loc = self.loc.abs() - outline = self.cell.outline() + outline = self.of.outline() - if self.reflect_horiz: + if self.reflect.horiz: (x0, x1) = ((loc.x - outline.xmax(), loc.x),) else: (x0, x1) = ((loc.x, loc.x + outline.xmax()),) - if self.reflect_vert: + if self.reflect.vert: (y0, y1) = ((loc.y - outline.ymax(), loc.y),) else: (y0, y1) = ((loc.y, loc.y + outline.ymax()),) diff --git a/Tetris/tetris/instantiable.py b/Tetris/tetris/instantiable.py new file mode 100644 index 0000000..2233e7c --- /dev/null +++ b/Tetris/tetris/instantiable.py @@ -0,0 +1,10 @@ +from typing import Union + +from .cell import Cell +from .array import Array +from .group import Group + +# Instantiable Types Union +# Primarily used as the `of` target of each `Instance` +Instantiable = Union[Cell, Array, Group] + diff --git a/Tetris/tetris/interface.rs b/Tetris/tetris/interface.rs deleted file mode 100644 index 83b5e50..0000000 --- a/Tetris/tetris/interface.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! -//! # Interfaces Module -//! -//! Describing Cells in terms of their IO Interfaces -//! - -// Crates.io Imports -use serde::{Deserialize, Serialize}; - -/// # Port -/// -/// Logical port, as in a netlist or HDL description. -/// Includes scalar, vector (bus), and bundle-valued ports. -/// Does not include physical/ geometric information. -/// -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Port { - /// Port Name - pub name: String, - /// Port Type & Content - pub kind: PortKind, -} -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PortKind { - /// Flat Scalar Port, e.g. `clk` - Scalar, - /// Array-Based Port, e.g. `data[31:0]` - Array { width: usize }, - /// Instance of a Hierarchical Bundle - Bundle { bundle_name: String }, -} -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Bundle { - pub name: String, - pub ports: Vec, -} diff --git a/Tetris/tetris/layer_period.py b/Tetris/tetris/layer_period.py deleted file mode 100644 index b3fb718..0000000 --- a/Tetris/tetris/layer_period.py +++ /dev/null @@ -1,175 +0,0 @@ -// Std-lib imports -use std::fmt::Debug; - -// Crates.io -use serde::{Deserialize, Serialize}; - -// Local imports -use crate::coords::{DbUnits, Xy}; -use crate::instance::Instance; -use crate::raw::{self, Dir, LayoutResult, Units}; -use crate::utils::Ptr; -use crate::{tracks::*, validate}; - - - -/// Transformed single period of [Track]s on a [Layer] -/// Splits track-info between signals and rails. -/// Stores each as a [Track] struct, which moves to a (start, width) size-format, -/// and includes a vector of track-segments for cutting and assigning nets. -#[derive(Debug, Clone, Default)] -pub struct LayerPeriod<'lib> { - pub index: usize, - pub signals: Vec>, - pub rails: Vec>, -} -impl<'lib> LayerPeriod<'lib> { - /// Shift the period by `dist` in its periodic direction - pub fn offset(&mut self, dist: DbUnits) -> LayoutResult<()> { - for t in self.rails.iter_mut() { - t.data.start += dist; - } - for t in self.signals.iter_mut() { - t.data.start += dist; - } - Ok(()) - } - /// Set the stop position for all [Track]s to `stop` - pub fn stop(&mut self, stop: DbUnits) -> LayoutResult<()> { - for t in self.rails.iter_mut() { - t.stop(stop)?; - } - for t in self.signals.iter_mut() { - t.stop(stop)?; - } - Ok(()) - } - /// Cut all [Track]s from `start` to `stop`, - pub fn cut( - &mut self, - start: DbUnits, - stop: DbUnits, - src: &'lib TrackCross, - ) -> TrackResult<()> { - for t in self.rails.iter_mut() { - t.cut(start, stop, src)?; - } - for t in self.signals.iter_mut() { - t.cut(start, stop, src)?; - } - Ok(()) - } - /// Block all [Track]s from `start` to `stop`, - pub fn block(&mut self, start: DbUnits, stop: DbUnits, src: &Ptr) -> TrackResult<()> { - for t in self.rails.iter_mut() { - t.block(start, stop, src)?; - } - for t in self.signals.iter_mut() { - t.block(start, stop, src)?; - } - Ok(()) - } -} - - -#[derive(Debug, Clone, Default)] -pub struct LayerPeriodData { - pub signals: Vec, - pub rails: Vec, -} -impl MetalLayer { - /// Convert this [Layer]'s track-info into a [LayerPeriodData] - pub(crate) fn to_layer_period_data(&self) -> LayoutResult { - let mut period = LayerPeriodData::default(); - let mut cursor = self.offset; - for e in &self.entries() { - let d = e.width; - match e.ttype { - TrackType::Gap => (), - TrackType::Rail(_railkind) => { - period.rails.push(TrackData { - ttype: e.ttype, - index: period.rails.len(), - dir: self.dir, - start: cursor, - width: d, - }); - } - TrackType::Signal => { - period.signals.push(TrackData { - ttype: e.ttype, - index: period.signals.len(), - dir: self.dir, - start: cursor, - width: d, - }); - } - }; - cursor += d; - } - Ok(period) - } - /// Convert this [Layer]'s track-info into a [LayerPeriod] - pub(crate) fn to_layer_period<'me, 'lib>( - &'me self, - index: usize, - stop: impl Into, - ) -> LayoutResult> { - let stop = stop.into(); - let mut period = LayerPeriod::default(); - period.index = index; - let mut cursor = self.offset + (self.pitch() * index); - let entries = self.entries(); - let iterator: Box> = - if self.flip == FlipMode::EveryOther && index % 2 == 1 { - Box::new(entries.iter().rev()) - } else { - Box::new(entries.iter()) - }; - for e in iterator { - let d = e.width; - match e.ttype { - TrackType::Gap => (), - TrackType::Rail(railkind) => { - period.rails.push( - Track { - data: TrackData { - ttype: e.ttype, - index: period.rails.len(), - dir: self.dir, - start: cursor, - width: d, - }, - segments: vec![TrackSegment { - tp: TrackSegmentType::Rail(railkind), - start: 0.into(), - stop, - }], - } - .validate()?, - ); - } - TrackType::Signal => { - period.signals.push( - Track { - data: TrackData { - ttype: e.ttype, - index: period.signals.len(), - dir: self.dir, - start: cursor, - width: d, - }, - segments: vec![TrackSegment { - tp: TrackSegmentType::Wire { src: None }, - start: 0.into(), - stop, - }], - } - .validate()?, - ); - } - }; - cursor += d; - } - Ok(period) - } \ No newline at end of file diff --git a/Tetris/tetris/layout.py b/Tetris/tetris/layout.py index f30f745..1ecdbc3 100644 --- a/Tetris/tetris/layout.py +++ b/Tetris/tetris/layout.py @@ -15,14 +15,14 @@ from .track_spec import TrackCross from .instance import Instance -# from .placeable import Placeable -# # Layout Cell Implementation -# -# A combination of lower-level cell instances and net-assignments to tracks. -# @dataclass class Layout: + """ + # Layout Cell Implementation + A combination of hierarchical instances and net-assignments to tracks. + """ + # Cell Name name: str # Number of Metal Layers Used @@ -30,19 +30,17 @@ class Layout: # Outline shape counted in x and y pitches of `stack` outline: Outline - # Layout Instances + # Instances of other layout objects (cells, arrays, etc.) instances: List[Instance] = field(default_factory=list) # Net-to-track assignments assignments: List[Assign] = field(default_factory=list) # Track cuts cuts: List[TrackCross] = field(default_factory=list) - # Placeable objects - places: List["Placeable"] = field(default_factory=list) # Assign a net at the given coordinates. def assign( self, net: str, layer: int, track: int, at: int, relz: RelZ, - ): + ) -> None: at = TrackCross.from_relz(layer, track, at, relz) self.assignments.append(Assign(net, at)) diff --git a/Tetris/tetris/net_handle.py b/Tetris/tetris/net_handle.py index 4704a11..c259553 100644 --- a/Tetris/tetris/net_handle.py +++ b/Tetris/tetris/net_handle.py @@ -1,5 +1,6 @@ - from pydantic.dataclasses import dataclass + +# Local imports from .layout import Layout from .relz import RelZ @@ -17,9 +18,6 @@ class NetHandle: # Assign our net at the given coordinates. # Consumes and returns `self` to enable chaining. - def at(self, layer: int, track: int, at: int, relz: RelZ) -> "NetHandle" { - self.parent.assign(&self.name, layer, track, at, relz); + def at(self, layer: int, track: int, at: int, relz: RelZ) -> "NetHandle": + self.parent.assign(self.name, layer, track, at, relz) return self - } -} - diff --git a/Tetris/tetris/placement.py b/Tetris/tetris/placement.py new file mode 100644 index 0000000..ea6dabb --- /dev/null +++ b/Tetris/tetris/placement.py @@ -0,0 +1,75 @@ +# +# # Layout21 Placement Module +# + +from typing import Union + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import PrimPitches, Xy +from .instance import Instance +from .side import Side +from .align import Align +from .separation import Separation + + +# # Relatively-Placed Assignment +# FIXME: merge back in with absoutely-placed [Assign] +@dataclass +class RelAssign: + net: str + loc: "RelativePlace" + + +@dataclass +class Port: + """ Reference to the Location of a Port """ + + inst: Instance + port: str + + +# Place-able types union +Placeable = Union[Instance, RelAssign, Port] + + +# Get the location of the placeable +def loc(self: Placeable) -> "Place": + if isinstance(self, Instance): + return self.loc + if isinstance(self, (Port, RelAssign)): + raise NotImplementedError # FIXME! + raise TypeError + + +# # Relative Placement +@dataclass +class RelativePlace: + # Placement is relative `to` this + to: Placeable + # Placed on this `side` of `to` + side: Side + # Aligned to this aspect of `to` + align: Align + # Separation between the placement and the `to` + sep: Separation + + +@dataclass +class AbsPlace: + """ Absolute-Valued Placement, in Primitive Pitches """ + + xy: Xy[PrimPitches] + + +# # Placement Union +# +# Includes absolute and relative placements. +# +# Absolute placements are in `Self.AbsType` units. +# Relative placements use the [RelativePlace] struct, +# which can be specified relative to any other [Placeable] object. +# +Place = Union[AbsPlace, RelativePlace] + diff --git a/Tetris/tetris/placement.rs b/Tetris/tetris/placement.rs deleted file mode 100644 index f793d26..0000000 --- a/Tetris/tetris/placement.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! -//! # Layout21 Placement Module -//! - -// Local imports -use crate::{ - array::ArrayInstance, - cell::Cell, - coords::{HasUnits, Int, PrimPitches, UnitSpeced, Xy}, - group::GroupInstance, - instance::Instance, - raw::{Dir, LayoutError, LayoutResult}, - utils::Ptr, -}; - -/// # Placement Enumeration -/// -/// Includes absolute and relative placements. -/// -/// Absolute placements are in `Self::AbsType` units. -/// Relative placements use the [RelativePlace] struct, -/// which can be specified relative to any other [Placeable] object. -/// -#[derive(Debug, Clone)] -pub enum Place { - /// Absolute - Abs(AbsType), - /// Relative - Rel(RelativePlace), -} -impl Place { - /// Assert that our place is absolute, and retrieve a shared reference to the inner [Xy] value. - pub fn abs(&self) -> LayoutResult<&T> { - match self { - Place::Abs(ref xy) => Ok(xy), - Place::Rel(_) => { - LayoutError::fail("Asserted absolute-placement on a relative-placement") - } - } - } - /// Assert that our place is absolute, and retrieve a mutable reference to the inner [Xy] value. - pub fn abs_mut(&mut self) -> LayoutResult<&mut T> { - match self { - Place::Abs(ref mut xy) => Ok(xy), - Place::Rel(_) => { - LayoutError::fail("Asserted absolute-placement on a relative-placement") - } - } - } -} -impl From> for Place> { - /// Convert [Xy] values into [Place::Abs] absolute places - fn from(xy: Xy) -> Self { - Self::Abs(xy) - } -} -impl From<(T, T)> for Place> { - /// Two-tuples of unit-specified numerics are converted to an [Xy] value. - fn from((x, y): (T, T)) -> Self { - Self::Abs(Xy::new(x, y)) - } -} -impl From<(Int, Int)> for Place> { - /// Two-tuples of integers are converted to an [Xy] value. - fn from(tup: (Int, Int)) -> Self { - Self::Abs(Xy::from(tup)) - } -} -impl From for Place { - fn from(rel: RelativePlace) -> Self { - Self::Rel(rel) - } -} - -/// # Relatively-Placed Assignment -/// FIXME: merge back in with absoutely-placed [Assign] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RelAssign { - pub net: String, - pub loc: RelativePlace, -} -/// # Relative Placement -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RelativePlace { - /// Placement is relative `to` this - pub to: Placeable, - /// Placed on this `side` of `to` - pub side: Side, - /// Aligned to this aspect of `to` - pub align: Align, - /// Separation between the placement and the `to` - pub sep: Separation, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Side { - Top, - Bottom, - Left, - Right, -} -impl Side { - /// Get the side rotated 90 degrees clockwise - pub fn cw_90(&self) -> Self { - match self { - Self::Top => Self::Right, - Self::Right => Self::Bottom, - Self::Bottom => Self::Left, - Self::Left => Self::Top, - } - } - /// Get the side rotated 90 degrees counter-clockwise - pub fn ccw_90(&self) -> Self { - match self { - Self::Top => Self::Left, - Self::Left => Self::Bottom, - Self::Bottom => Self::Right, - Self::Right => Self::Top, - } - } -} -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Align { - /// Side-to-side alignment - Side(Side), - /// Center-aligned - Center, - /// Port-to-port alignment - Ports(String, String), -} - -/// Enumerated means of specifying relative-placement separation -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SepBy { - /// Separated by [UnitSpeced]-distance in x and y, and by layers in z - UnitSpeced(UnitSpeced), - /// Separated by the size of another Cell - SizeOf(Ptr), -} -/// Three-dimensional separation units -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct Separation { - pub x: Option, - pub y: Option, - pub z: Option, -} -impl Separation { - pub fn new(x: Option, y: Option, z: Option) -> Self { - Self { x, y, z } - } - pub fn x(x: SepBy) -> Self { - Self { - x: Some(x), - ..Default::default() - } - } - pub fn y(y: SepBy) -> Self { - Self { - y: Some(y), - ..Default::default() - } - } - pub fn z(z: isize) -> Self { - Self { - z: Some(z), - ..Default::default() - } - } - /// Get the separation in direction `dir` - pub fn dir(&self, dir: Dir) -> &Option { - match dir { - Dir::Horiz => &self.x, - Dir::Vert => &self.y, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Placeable { - /// Instance of another cell - Instance(Ptr), - /// Uniform array of placeable elements - Array(Ptr), - /// Group of other placeable elements - Group(Ptr), - /// Instance port location - Port { inst: Ptr, port: String }, - /// Assignment - Assign(Ptr), -} -impl Placeable { - /// Get the location of the placeable - pub fn loc(&self) -> LayoutResult>> { - let loc = match self { - Placeable::Instance(ref p) => { - let p = p.read()?; - p.loc.clone() - } - Placeable::Array(ref p) => { - let p = p.read()?; - p.loc.clone() - } - Placeable::Group(ref p) => { - let p = p.read()?; - p.loc.clone() - } - Placeable::Port { .. } => unimplemented!(), - Placeable::Assign(_) => unimplemented!(), - }; - Ok(loc) - } -} diff --git a/Tetris/tetris/separation.py b/Tetris/tetris/separation.py new file mode 100644 index 0000000..a30b7ec --- /dev/null +++ b/Tetris/tetris/separation.py @@ -0,0 +1,50 @@ +from typing import Union, List, Optional + +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import UnitSpeced, Dir + + +@dataclass +class Instantiable: + ... # FIXME! + + +@dataclass +class SizeOf: + of: Instantiable + + +# Enumerated means of specifying x-y relative-placement separation +SepBy = Union[ + UnitSpeced, SizeOf # Absolute valued dimensions # Size of an instantiable object +] + +# Three-dimensional separation units +@dataclass +class Separation: + x: Optional[SepBy] = None + y: Optional[SepBy] = None + z: Optional[int] = None + + @staticmethod + def by_x(x: SepBy) -> "Separation": + return Separation(x=x) + + @staticmethod + def by_y(y: SepBy) -> "Separation": + return Separation(y=y) + + @staticmethod + def by_z(z: int) -> "Separation": + return Separation(z=z) + + # Get the separation in direction `dir` + def dir(self, dir_: Dir) -> Optional[SepBy]: + if dir_ == Dir.Horiz: + return self.x + if dir_ == Dir.Vert: + return self.y + raise ValueError + diff --git a/Tetris/tetris/side.py b/Tetris/tetris/side.py new file mode 100644 index 0000000..3e7abaf --- /dev/null +++ b/Tetris/tetris/side.py @@ -0,0 +1,33 @@ +from enum import Enum, auto + + +class Side(Enum): + Top = auto() + Bottom = auto() + Left = auto() + Right = auto() + + # Get the side rotated 90 degrees clockwise + def cw_90(self) -> "Side": + if self == Side.Top: + return Side.Right + if self == Side.Right: + return Side.Bottom + if self == Side.Bottom: + return Side.Left + if self == Side.Left: + return Side.Top + raise ValueError + + # Get the side rotated 90 degrees counter-clockwise + def ccw_90(self) -> "Side": + if self == Side.Top: + return Side.Left + if self == Side.Left: + return Side.Bottom + if self == Side.Bottom: + return Side.Right + if self == Side.Right: + return Side.Top + raise ValueError + diff --git a/Tetris/tetris/stack.py b/Tetris/tetris/stack.py index d775819..27025fc 100644 --- a/Tetris/tetris/stack.py +++ b/Tetris/tetris/stack.py @@ -5,16 +5,12 @@ from pydantic.dataclasses import dataclass # Local imports -from .index import Index +from .index import Index +from .units import Units from .coords import DbUnits, Xy, Dir from .track_spec import TrackCross, TrackSpec, TrackEntry, Repeat -@dataclass -class Units: - ... # FIXME! - - # # Via Targets # # Enumerates the things vias can "go between". diff --git a/Tetris/tetris/units.py b/Tetris/tetris/units.py new file mode 100644 index 0000000..b3090d4 --- /dev/null +++ b/Tetris/tetris/units.py @@ -0,0 +1,7 @@ +from pydantic.dataclasses import dataclass + + +@dataclass +class Units: + ... # FIXME! + From 981e4760f9663c70f400fc1d4e6ae6e1bdee3c70 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 30 Jun 2022 21:22:07 -0700 Subject: [PATCH 17/28] WIP, Near Complete Tetris Porting --- Tetris/tests/mod.rs | 229 ----------------------------------- Tetris/tests/test_tetris1.py | 217 +++++++++++++++++++++++++++++++++ Tetris/tetris/cell.py | 3 +- Tetris/tetris/library.py | 3 +- Tetris/tetris/outline.py | 4 +- 5 files changed, 223 insertions(+), 233 deletions(-) delete mode 100644 Tetris/tests/mod.rs create mode 100644 Tetris/tests/test_tetris1.py diff --git a/Tetris/tests/mod.rs b/Tetris/tests/mod.rs deleted file mode 100644 index 7f53447..0000000 --- a/Tetris/tests/mod.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! -//! # Unit Tests -//! - -// Local imports -use crate::{ - abs, cell::Cell, conv, instance::Instance, layout::Layout, library::Library, outline::Outline, - raw::LayoutResult, stack::*, tracks::*, utils::PtrList, validate::ValidStack, -}; - -// Modules -pub mod demos; -pub mod ro; -pub mod stacks; -use stacks::SampleStacks; - -/// Create an empty cell -#[test] -fn empty_cell() -> LayoutResult<()> { - let c = Layout { - name: "EmptyCell".into(), - metals: 5, - outline: Outline::rect(50, 5)?, - instances: PtrList::new(), - assignments: Vec::new(), - cuts: Vec::new(), - places: Vec::new(), - }; - let mut lib = Library::new("EmptyCellLib"); - let _c2 = lib.cells.insert(Cell::from(c)); - exports(lib, SampleStacks::pdka()?) -} -/// Create a layout-implementation -#[test] -fn create_layout() -> LayoutResult<()> { - Layout { - name: "HereGoes".into(), - metals: 4, - outline: Outline::rect(50, 5)?, - instances: PtrList::new(), - assignments: vec![Assign { - net: "clk".into(), - at: TrackCross::from_relz(1, 0, 1, RelZ::Above), - }], - cuts: Vec::new(), - places: Vec::new(), - }; - Ok(()) -} -/// Create a library -#[test] -fn create_lib1() -> LayoutResult<()> { - let mut lib = Library::new("lib1"); - - lib.cells.insert(Layout { - name: "HereGoes".into(), - metals: 3, - outline: Outline::rect(50, 5)?, - instances: PtrList::new(), - assignments: vec![Assign { - net: "clk".into(), - at: TrackCross::from_relz(1, 4, 2, RelZ::Below), - }], - cuts: vec![ - TrackCross::from_relz(0, 1, 1, RelZ::Above), - TrackCross::from_relz(0, 1, 3, RelZ::Above), - TrackCross::from_relz(0, 1, 5, RelZ::Above), - TrackCross::from_relz(1, 1, 1, RelZ::Below), - TrackCross::from_relz(1, 1, 3, RelZ::Below), - TrackCross::from_relz(1, 1, 5, RelZ::Below), - ], - places: Vec::new(), - }); - exports(lib, SampleStacks::pdka()?) -} -/// Create a cell with instances -#[test] -fn create_lib2() -> LayoutResult<()> { - let mut lib = Library::new("lib2"); - let c2 = Layout::new("IsInst", 2, Outline::rect(100, 10)?); - let c2 = lib.cells.insert(c2); - - lib.cells.insert(Layout { - name: "HasInst".into(), - metals: 4, - outline: Outline::rect(200, 20)?, - instances: vec![Instance { - inst_name: "inst1".into(), - cell: c2, - loc: (20, 2).into(), - reflect_horiz: false, - reflect_vert: false, - }] - .into(), - assignments: vec![Assign { - net: "clk".into(), - at: TrackCross::from_relz(1, 1, 1, RelZ::Above), - }], - cuts: Vec::new(), - places: Vec::new(), - }); - exports(lib, SampleStacks::pdka()?) -} - -/// Create an abstract layout, with its variety of supported port types -#[test] -fn create_abstract() -> LayoutResult<()> { - let outline = Outline::rect(11, 11)?; - let ports = vec![ - abs::Port { - name: "edge_bot".into(), - kind: abs::PortKind::Edge { - layer: 2, - track: 2, - side: abs::Side::BottomOrLeft, - }, - }, - abs::Port { - name: "edge_top".into(), - kind: abs::PortKind::Edge { - layer: 2, - track: 4, - side: abs::Side::TopOrRight, - }, - }, - abs::Port { - name: "edge_left".into(), - kind: abs::PortKind::Edge { - layer: 1, - track: 1, - side: abs::Side::BottomOrLeft, - }, - }, - abs::Port { - name: "edge_right".into(), - kind: abs::PortKind::Edge { - layer: 1, - track: 5, - side: abs::Side::TopOrRight, - }, - }, - ]; - abs::Abstract { - name: "abstrack".into(), - outline, - metals: 4, - ports, - }; - Ok(()) -} - -/// Create a cell with abstract instances -#[test] -fn create_lib3() -> LayoutResult<()> { - let mut lib = Library::new("lib3"); - - let c2 = lib.cells.insert(abs::Abstract { - name: "IsAbs".into(), - metals: 1, - outline: Outline::rect(100, 10)?, - ports: Vec::new(), - }); - - lib.cells.insert(Layout { - name: "HasAbss".into(), - metals: 4, - outline: Outline::rect(500, 50)?, - instances: vec![ - Instance { - inst_name: "inst1".into(), - cell: c2.clone(), - loc: (0, 0).into(), - reflect_horiz: false, - reflect_vert: false, - }, - Instance { - inst_name: "inst2".into(), - cell: c2.clone(), - loc: (200, 20).into(), - reflect_horiz: false, - reflect_vert: false, - }, - Instance { - inst_name: "inst4".into(), - cell: c2.clone(), - loc: (400, 40).into(), - reflect_horiz: false, - reflect_vert: false, - }, - ] - .into(), - assignments: Vec::new(), - cuts: Vec::new(), - places: Vec::new(), - }); - exports(lib, SampleStacks::pdka()?) -} -/// Helper function. Export [Library] `lib` in several formats. -pub fn exports(lib: Library, stack: ValidStack) -> LayoutResult<()> { - // Serializable formats will generally be written as YAML. - use crate::utils::SerializationFormat::Yaml; - - let rawlib = conv::raw::RawExporter::convert(lib, stack)?; - let rawlib = rawlib.read()?; - - // Export to ProtoBuf, save as YAML and binary - let protolib = rawlib.to_proto()?; - Yaml.save( - &protolib, - &resource(&format!("{}.proto.yaml", &protolib.domain)), - ) - .unwrap(); - crate::raw::proto::proto::save( - &protolib, - &resource(&format!("{}.proto.bin", &protolib.domain)), - ) - .unwrap(); - - // Export to GDSII - let gds = rawlib.to_gds()?; - Yaml.save(&gds, &resource(&format!("{}.gds.yaml", &gds.name))) - .unwrap(); - gds.save(&resource(&format!("{}.gds", &gds.name)))?; - Ok(()) -} -/// Grab the full path of resource-file `fname` -fn resource(rname: &str) -> String { - format!("{}/resources/{}", env!("CARGO_MANIFEST_DIR"), rname) -} diff --git a/Tetris/tests/test_tetris1.py b/Tetris/tests/test_tetris1.py new file mode 100644 index 0000000..b5cfc7f --- /dev/null +++ b/Tetris/tests/test_tetris1.py @@ -0,0 +1,217 @@ +# +# # Unit Tests +# + +# Local imports +from tetris import abstract +from tetris.cell import Cell +from tetris.instance import Instance +from tetris.layout import Layout +from tetris.library import Library +from tetris.outline import Outline +from tetris.relz import RelZ +from tetris.stack import * + +# from tetris import conv +# from tetris.tracks import * +# from tetris.validate import ValidStack + + +# Create an empty cell +def test_empty_cell() -> None: + c = Layout( + name="EmptyCell", + metals=5, + outline=Outline.rect(50, 5), + instances=list(), + assignments=list(), + cuts=list(), + ) + lib = Library("EmptyCellLib") + lib.cells[c.name] = Cell.from_views(c.name, [c]) + exports(lib, SampleStacks.pdka()) + + +# Create a layout-implementation +def test_create_layout() -> None: + Layout( + name="HereGoes", + metals=4, + outline=Outline.rect(50, 5), + instances=list(), + assignments=[Assign(net="clk", at=TrackCross.from_relz(1, 0, 1, RelZ.Above),)], + cuts=list(), + ) + + +# Create a library +def test_create_lib1() -> None: + lib = Library("lib1") + layout = Layout( + name="HereGoes", + metals=3, + outline=Outline.rect(50, 5), + instances=list(), + assignments=[Assign(net="clk", at=TrackCross.from_relz(1, 4, 2, RelZ.Below),)], + cuts=[ + TrackCross.from_relz(0, 1, 1, RelZ.Above), + TrackCross.from_relz(0, 1, 3, RelZ.Above), + TrackCross.from_relz(0, 1, 5, RelZ.Above), + TrackCross.from_relz(1, 1, 1, RelZ.Below), + TrackCross.from_relz(1, 1, 3, RelZ.Below), + TrackCross.from_relz(1, 1, 5, RelZ.Below), + ], + ) + lib.cells[layout.name] = Cell.from_views(layout.name, [layout]) + exports(lib, SampleStacks.pdka()) + + +# # Create a cell with instances +# def test_create_lib2() -> None : +# lib = Library("lib2") +# c2 = Layout("IsInst", 2, Outline.rect(100, 10)) +# c2 = lib.cells.insert(c2) + +# lib.cells.insert(Layout : +# name: "HasInst", +# metals: 4, +# outline: Outline.rect(200, 20), +# instances: [Instance : +# inst_name: "inst1", +# cell: c2, +# loc: (20, 2), +# reflect_horiz: false, +# reflect_vert: false, +# ] +# , +# assignments: [Assign : +# net: "clk", +# at: TrackCross.from_relz(1, 1, 1, RelZ.Above), +# ], +# cuts: list(), +# places: list(), +# ) +# exports(lib, SampleStacks.pdka()) + + +# # Create an abstract layout, with its variety of supported port types +# def test_create_abstract() -> None : +# outline = Outline.rect(11, 11) +# ports = [ +# abs.Port : +# name: "edge_bot", +# kind: abs.PortKind.Edge : +# layer: 2, +# track: 2, +# side: abs.Side.BottomOrLeft, +# , +# , +# abs.Port : +# name: "edge_top", +# kind: abs.PortKind.Edge : +# layer: 2, +# track: 4, +# side: abs.Side.TopOrRight, +# , +# , +# abs.Port : +# name: "edge_left", +# kind: abs.PortKind.Edge : +# layer: 1, +# track: 1, +# side: abs.Side.BottomOrLeft, +# , +# , +# abs.Port : +# name: "edge_right", +# kind: abs.PortKind.Edge : +# layer: 1, +# track: 5, +# side: abs.Side.TopOrRight, +# , +# , +# ] +# abs.Abstract : +# name: "abstrack", +# outline, +# metals: 4, +# ports, + + +# # Create a cell with abstract instances +# def test_create_lib3() -> None : +# lib = Library("lib3") + +# c2 = lib.cells.insert(abs.Abstract : +# name: "IsAbs", +# metals: 1, +# outline: Outline.rect(100, 10), +# ports: list(), +# ) + +# lib.cells.insert(Layout : +# name: "HasAbss", +# metals: 4, +# outline: Outline.rect(500, 50), +# instances: [ +# Instance : +# inst_name: "inst1", +# cell: c2.clone(), +# loc: (0, 0), +# reflect_horiz: false, +# reflect_vert: false, +# , +# Instance : +# inst_name: "inst2", +# cell: c2.clone(), +# loc: (200, 20), +# reflect_horiz: false, +# reflect_vert: false, +# , +# Instance : +# inst_name: "inst4", +# cell: c2.clone(), +# loc: (400, 40), +# reflect_horiz: false, +# reflect_vert: false, +# , +# ] +# , +# assignments: list(), +# cuts: list(), +# places: list(), +# ) +# exports(lib, SampleStacks.pdka()) + +# # Helper function. Export [Library] `lib` in several formats. +# def exports(lib: Library, stack: ValidStack) -> None : +# # Serializable formats will generally be written as YAML. +# from . import utils.SerializationFormat.Yaml + +# rawlib = conv.raw.RawExporter.convert(lib, stack) +# rawlib = rawlib.read() + +# # Export to ProtoBuf, save as YAML and binary +# protolib = rawlib.to_proto() +# Yaml.save( +# protolib, +# resource(format!(":.proto.yaml", protolib.domain)), +# ) + +# raw.proto.proto.save( +# protolib, +# resource(format!(":.proto.bin", protolib.domain)), +# ) + + +# # Export to GDSII +# gds = rawlib.to_gds() +# Yaml.save(gds, resource(format!(":.gds.yaml", gds.name))) + +# gds.save(resource(format!(":.gds", gds.name))) + + +# # Grab the full path of resource-file `fname` +# def resource(rname: str) -> str : +# format!(":/resources/:", env!("CARGO_MANIFEST_DIR"), rname) + diff --git a/Tetris/tetris/cell.py b/Tetris/tetris/cell.py index 6f97509..aa3e997 100644 --- a/Tetris/tetris/cell.py +++ b/Tetris/tetris/cell.py @@ -68,7 +68,8 @@ def add_view(self, view: CellView): self.abs = view elif isinstance(view, Bundle): self.interface = view - raise TypeError + else: + raise TypeError(f"Invalid Cell View {view} of type {type(view)}") # Create from a list of [CellView]s and a name. def from_views(name: str, views: List[CellView]) -> "Cell": diff --git a/Tetris/tetris/library.py b/Tetris/tetris/library.py index d248f8b..51ffe19 100644 --- a/Tetris/tetris/library.py +++ b/Tetris/tetris/library.py @@ -3,6 +3,7 @@ # from typing import List, Set, Dict +from dataclasses import field from pydantic.dataclasses import dataclass @@ -18,7 +19,7 @@ class Library: # Library Name name: str # Cell Definitions - cells: Dict[str, Cell] + cells: Dict[str, Cell] = field(default_factory=dict) # FIXME: `raw` stuff # # [raw.Library] Definitions diff --git a/Tetris/tetris/outline.py b/Tetris/tetris/outline.py index 46ae9a2..df4ef48 100644 --- a/Tetris/tetris/outline.py +++ b/Tetris/tetris/outline.py @@ -64,8 +64,8 @@ def from_prim_pitches(x: List[PrimPitches], y: List[PrimPitches]) -> "Outline": @staticmethod def rect(x: int, y: int) -> "Outline": - # Create a new rectangular outline of dimenions `x` by `y` - return Outline([x], [y]) + """ Create a new rectangular outline of dimenions `x` by `y`""" + return Outline([PrimPitches(Dir.Horiz, x)], [PrimPitches(Dir.Vert, y)]) # Maximum x-coordinate # (Which is also always the *first* x-coordinate) From ead832f9d1e9501c849fae0fce09aa7c7f7b8501 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Fri, 1 Jul 2022 13:19:42 -0700 Subject: [PATCH 18/28] Add utils post-fix Unwrapper trait, and a single use-case thereof --- layout21raw/src/gds.rs | 14 ++--- layout21utils/src/error.rs | 118 ++++++++++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 8 deletions(-) diff --git a/layout21raw/src/gds.rs b/layout21raw/src/gds.rs index ce581ce..37ebcd6 100644 --- a/layout21raw/src/gds.rs +++ b/layout21raw/src/gds.rs @@ -17,7 +17,7 @@ use crate::{ bbox::BoundBoxTrait, error::{LayoutError, LayoutResult}, geom::{Path, Point, Polygon, Rect, Shape, ShapeTrait}, - utils::{ErrorContext, ErrorHelper, Ptr}, + utils::{ErrorContext, ErrorHelper, Ptr, Unwrapper}, Abstract, AbstractPort, Cell, Dir, Element, Instance, Int, LayerKey, LayerPurpose, Layers, Layout, Library, TextElement, Units, }; @@ -206,10 +206,10 @@ impl<'lib> GdsExporter<'lib> { purpose: &LayerPurpose, ) -> LayoutResult { let layers = self.lib.layers.read()?; - let layer = self.unwrap( - layers.get(*layer), + let layer = layers.get(*layer).or_handle( + self, format!("Layer {:?} Not Defined in Library {}", layer, self.lib.name), - )?; + )?; let xtype = self .unwrap( layer.num(purpose), @@ -354,9 +354,9 @@ impl ErrorHelper for GdsExporter<'_> { /// /// Trait for calculating the location of text-labels, generally per [Shape]. /// -/// Sole function `label_location` calculates an appropriate location, -/// or returns a [LayoutError] if one cannot be found. -/// +/// Sole function `label_location` calculates an appropriate location, +/// or returns a [LayoutError] if one cannot be found. +/// /// While Layout21 formats do not include "placed text", GDSII relies on it for connectivity annotations. /// How to place these labels varies by shape type. /// diff --git a/layout21utils/src/error.rs b/layout21utils/src/error.rs index 3af8e31..0ea1096 100644 --- a/layout21utils/src/error.rs +++ b/layout21utils/src/error.rs @@ -1,11 +1,42 @@ //! //! # Layout21 Error-Helper Utilities //! +//! ```rust +//! use layout21utils::error::{ErrorHelper, Unwrapper}; +//! +//! /// Example implementer of [`ErrorHelper`]. +//! /// Typical implementers will have some internal state to report upon failure. +//! struct HasFunErrors; +//! impl ErrorHelper for HasFunErrors { +//! type Error = String; +//! +//! /// Add our extra-fun state upon failure. +//! fn err(&self, msg: impl Into) -> Self::Error { +//! format!("Extra Fun Error: {}", msg.into()) +//! } +//! } +//! impl HasFunErrors { +//! /// Demo of using the [`Unwrapper`] trait on [`Option`]s and [`Result`]s. +//! fn fun(&self) -> Result<(), String> { +//! // Unwrap an [`Option`] +//! Some(5).or_handle(self, "Option failed!")?; +//! +//! // Unwrap a [`Result`] +//! let r: Result<(), String> = Ok(()); +//! r.or_handle(self, "Result failed!") +//! } +//! } +//! ``` +//! -/// Helper trait for re-use among our many conversion tree-walkers. +/// +/// # ErrorHelper +/// +/// Helper trait for re-use among many conversion tree-walkers. /// Each implementer will generally have some internal state to report upon failure, /// which it can inject in the implementation-required `err` method. /// The `fail` method, provided by default, simply returns the `err` value. +/// pub trait ErrorHelper { type Error; @@ -15,6 +46,16 @@ pub trait ErrorHelper { fn fail(&self, msg: impl Into) -> Result { Err(self.err(msg)) } + /// Wrap an existing error, with a paired message, into our [`Error`] type. + /// "Optional" method which must be user-implemented to be used. + fn wrap_err( + &self, + _err: impl std::error::Error, + _msg: impl Into, + ) -> Result { + // Default implementation fails. Implement to use! + unimplemented!() + } /// Unwrap the [Option] `opt` if it is [Some], and return our error if not. fn unwrap(&self, opt: Option, msg: impl Into) -> Result { match opt { @@ -39,6 +80,81 @@ pub trait ErrorHelper { self.fail(msg) // Default version always fails. } } + +/// +/// # Unwrapper +/// +/// Trait for post-fix application of [`ErrorHelper`] handling, +/// during the particularly common cases of unwrapping [`Option`]s and [`Result`]s. +/// +/// Sole method `or_handle` takes an [`ErrorHelper`] and string-convertible error-message as arguments, +/// and returns a [`Result`] of the [`ErrorHelper`]'s associated error type. +/// +/// Example: +/// +/// ```rust +/// use layout21utils::error::{ErrorHelper, Unwrapper}; +/// +/// fn example(h: &impl ErrorHelper) -> Result<(), String> { +/// // Unwrap an [`Option`] +/// Some(5).or_handle(h, "Option failed!")?; +/// +/// // Unwrap a [`Result`] +/// let r: Result<(), String> = Ok(()); +/// r.or_handle(h, "Result failed!") +/// } +/// ``` +/// +/// The typical usage of [`Unwrapper`] is not to implement it for new types, +/// but to just import the trait and use it on the standard library [`Option`] and [`Result`] types. +/// And while not required, said usages are generally expected to be in the +/// implementations of [`ErrorHelper`] types. +/// +pub trait Unwrapper { + type Ok; + fn or_handle(self, helper: &H, msg: impl Into) -> Result + where + H: ErrorHelper; +} + +/// # Unwrapper for [`Option`] +/// +/// Performs an action similar to [`Option.unwrap`], mapping to the paired [`ErrorHelper`] error. +/// +impl Unwrapper for Option { + type Ok = T; + fn or_handle(self, helper: &H, msg: impl Into) -> Result + where + H: ErrorHelper, + { + match self { + Some(t) => Ok(t), + None => helper.fail(msg), + } + } +} + +/// # Unwrapper for [`Result`] +/// +/// Performs an action similar to [`Result.unwrap`], mapping to the paired [`ErrorHelper`] error. +/// +impl Unwrapper for Result { + type Ok = T; + fn or_handle( + self, + helper: &H, + msg: impl Into, + ) -> Result< as Unwrapper>::Ok, H::Error> + where + H: ErrorHelper, + { + match self { + Ok(t) => Ok(t), + Err(_) => helper.fail(msg), + } + } +} + /// Enumerated conversion contexts /// Generally used for error reporting #[derive(Debug, Clone)] From adabe6191f645ec02489e8d5dc8675af004609a7 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 24 Jan 2023 17:53:16 -0800 Subject: [PATCH 19/28] More python porting. Basic relative-placer tests passing(!) --- Tetris/tests/demos.rs | 21 - Tetris/tests/test_placer.py | 396 +++++++++++ Tetris/tests/test_tetris.py | 1 - Tetris/tests/test_tetris1.py | 28 +- Tetris/tetris/__init__.py | 1 + Tetris/tetris/align.py | 1 - Tetris/tetris/array.py | 7 +- Tetris/tetris/bbox.py | 42 +- Tetris/tetris/bundle.py | 1 - Tetris/tetris/cell.py | 9 +- Tetris/tetris/conv/layer_period.py | 210 +++--- Tetris/tetris/conv/tracks.py | 24 +- Tetris/tetris/coords.py | 38 +- Tetris/tetris/error.py | 1 - Tetris/tetris/group.py | 1 - Tetris/tetris/index.py | 1 - Tetris/tetris/instance.py | 58 +- Tetris/tetris/instantiable.py | 1 - Tetris/tetris/layout.py | 11 +- Tetris/tetris/library.py | 60 +- Tetris/tetris/outline.py | 12 +- Tetris/tetris/placement.py | 35 +- Tetris/tetris/placer.py | 357 ++++++++++ Tetris/tetris/placer.rs | 1000 ---------------------------- Tetris/tetris/reflect.py | 30 + Tetris/tetris/relz.py | 3 +- Tetris/tetris/separation.py | 5 +- Tetris/tetris/side.py | 1 - Tetris/tetris/stack.py | 4 + Tetris/tetris/track_spec.py | 8 +- Tetris/tetris/units.py | 1 - 31 files changed, 1081 insertions(+), 1287 deletions(-) delete mode 100644 Tetris/tests/demos.rs create mode 100644 Tetris/tests/test_placer.py create mode 100644 Tetris/tetris/placer.py delete mode 100644 Tetris/tetris/placer.rs create mode 100644 Tetris/tetris/reflect.py diff --git a/Tetris/tests/demos.rs b/Tetris/tests/demos.rs deleted file mode 100644 index ee1fb15..0000000 --- a/Tetris/tests/demos.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Local imports -use super::{exports, stacks::SampleStacks}; -use crate::{ - conv::proto::ProtoLibImporter, protos, raw::LayoutResult, utils::SerializationFormat::Yaml, -}; - -#[test] -fn empty() -> LayoutResult<()> { - behind_curtain(include_str!("empty.yaml")) -} - -#[test] -fn insts() -> LayoutResult<()> { - behind_curtain(include_str!("insts.yaml")) -} - -fn behind_curtain(yaml: &str) -> LayoutResult<()> { - let plib: protos::tetris::Library = Yaml.from_str(yaml)?; - let lib = ProtoLibImporter::import(&plib)?; - exports(lib, SampleStacks::pdka()?) -} diff --git a/Tetris/tests/test_placer.py b/Tetris/tests/test_placer.py new file mode 100644 index 0000000..45c6fc8 --- /dev/null +++ b/Tetris/tests/test_placer.py @@ -0,0 +1,396 @@ +from dataclasses import dataclass +from typing import Optional + +import tetris +from tetris.align import Align, AlignSide +from tetris.abstract import Abstract, Port, PortKind +from tetris.library import Library +from tetris.instance import Instance, Reflect +from tetris.layout import Layout +from tetris.placer import Placer +from tetris.outline import Outline +from tetris.cell import Cell +from tetris.placement import Place, Placeable, RelAssign, RelativePlace, Side, AbsPlace +from tetris.separation import SepBy, Separation + +# from .tests import exports, SampleStacks + + +def exports(lib: Library, stack: Optional["Stack"] = None) -> None: + ... # FIXME! + + +class SampleStacks: + @classmethod + def empty(cls) -> None: + return None + + @classmethod + def pdka(cls) -> None: + return None + + +def test_place1() -> None: + # Most basic smoke-test + Placer.place(Library("plib"), SampleStacks.empty()) + + +def test_place2() -> None: + # Initial test of relative placement + + lib = Library("test_place2") + # Create a unit cell which we'll instantiate a few times + unit = Layout("unit", 0, Outline.rect(3, 7)) + unit = Cell(name="unit", layout=unit) + unit = lib.add_cell(unit) + # Create an initial instance + i0 = Instance( + name="i0", + of=unit, + loc=AbsPlace.xy(47, 51), + reflect=Reflect.default(), + ) + # Create the parent cell which instantiates it + parent = Layout("parent", 0, Outline.rect(100, 100)) + i0 = parent.add_instance(i0) + # Create another Instance, placed relative to `i0` + i1 = Instance( + name="i1", + of=unit, + loc=RelativePlace( + to=i0, + side=Side.Right, + align=AlignSide(Side.Bottom), + sep=Separation.zero(), + ), + reflect=Reflect.default(), + ) + i1 = parent.add_instance(i1) + parent = Cell(name="parent", layout=parent) + parent = lib.add_cell(parent) + + # The real code-under-test: run placement + (lib, stack) = Placer.place(lib, SampleStacks.empty()) + + # Checks on results + assert len(lib.cells) == 2 + assert lib.cells["unit"] is unit + assert lib.cells["parent"] is parent + # Now check the locations + parent_layout = parent.layout + assert parent_layout.instances[0].loc == AbsPlace.xy(47, 51) + assert parent_layout.instances[1].loc.resolved == AbsPlace.xy(50, 51) + exports(lib, stack) + + +# def test_place3() -> None : +# # Test each relative side and alignment + +# # Get the sample data +# sample_lib = SampleLib.get() +# ibig = sample_lib.ibig +# big = sample_lib.big +# lil = sample_lib.lil +# lib = sample_lib.lib +# parent = sample_lib.parent + +# lib.name = "test_place3" +# relto = ibig + +# # Relative-placement-adder closure +# add_inst = |inst_name: str, side, align| { +# i = Instance( +# inst_name: inst_name, +# cell: lil, +# loc: RelativePlace(RelativePlace( +# to: relto, +# side, +# align: AlignSide(align), +# sep: Separation.zero(), +# ), +# reflect_horiz: False, +# reflect_vert: False, +# ) +# parent.add_instance(i) +# } +# # Add a bunch of em +# i1 = add_inst("i1", Side.Left, Side.Bottom) +# i2 = add_inst("i2", Side.Right, Side.Bottom) +# i3 = add_inst("i3", Side.Bottom, Side.Left) +# i4 = add_inst("i4", Side.Bottom, Side.Right) +# i5 = add_inst("i5", Side.Left, Side.Top) +# i6 = add_inst("i6", Side.Right, Side.Top) +# i7 = add_inst("i7", Side.Top, Side.Left) +# i8 = add_inst("i8", Side.Top, Side.Right) + +# # Add `parent` to the library +# _parent = lib.add_cell(parent) + +# # The real code under test: run placement +# (lib, stack) = Placer.place(lib, SampleStacks.pdka()) + +# # And test the placed results +# bigbox = ibig.read().boundbox() +# ibox = i1.read().boundbox() +# assert ibox.side(Side.Right), bigbox.side(Side.Left)) +# assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) +# ibox = i2.read().boundbox() +# assert ibox.side(Side.Left), bigbox.side(Side.Right)) +# assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) +# ibox = i3.read().boundbox() +# assert ibox.side(Side.Top), bigbox.side(Side.Bottom)) +# assert ibox.side(Side.Left), bigbox.side(Side.Left)) +# ibox = i4.read().boundbox() +# assert ibox.side(Side.Top), bigbox.side(Side.Bottom)) +# assert ibox.side(Side.Right), bigbox.side(Side.Right)) +# ibox = i5.read().boundbox() +# assert ibox.side(Side.Right), bigbox.side(Side.Left)) +# assert ibox.side(Side.Top), bigbox.side(Side.Top)) +# ibox = i6.read().boundbox() +# assert ibox.side(Side.Left), bigbox.side(Side.Right)) +# assert ibox.side(Side.Top), bigbox.side(Side.Top)) +# ibox = i7.read().boundbox() +# assert ibox.side(Side.Bottom), bigbox.side(Side.Top)) +# assert ibox.side(Side.Left), bigbox.side(Side.Left)) +# ibox = i8.read().boundbox() +# assert ibox.side(Side.Bottom), bigbox.side(Side.Top)) +# assert ibox.side(Side.Right), bigbox.side(Side.Right)) +# exports(lib, stack) + + +# def test_place4() -> None : +# # Test size-of separation + +# # Get the sample data +# sample_lib = SampleLib.get() +# ibig = sample_lib.ibig +# big = sample_lib.big +# lil = sample_lib.lil +# lib = sample_lib.lib +# parent = sample_lib.parent + +# lib.name = "test_place4" + +# # Relative-placement-adder closure +# add_inst = |inst_name: str, side, sep| { +# i = Instance( +# inst_name: inst_name, +# cell: lil, +# loc: RelativePlace(RelativePlace( +# to: ibig, +# side, +# align: AlignSide(side.cw_90()), # Leave out `align` as an arg, set one-turn CW of `side` +# sep, +# ), +# reflect_horiz: False, +# reflect_vert: False, +# ) +# parent.add_instance(i) +# } +# # Add a bunch of em +# sep_x = Separation.x(SepBy.SizeOf(lil)) +# i1 = add_inst("i1", Side.Left, sep_x) +# i2 = add_inst("i2", Side.Right, sep_x) +# sep_y = Separation.y(SepBy.SizeOf(lil)) +# i3 = add_inst("i3", Side.Bottom, sep_y) +# i4 = add_inst("i4", Side.Top, sep_y) +# # Add `parent` to the library +# _parent = lib.add_cell(parent) + +# # The real code under test: run placement +# (lib, stack) = Placer.place(lib, SampleStacks.pdka()) + +# # And test the placed results +# lilsize = lil.read().boundbox_size() +# bigbox = ibig.read().boundbox() +# ibox = i1.read().boundbox() +# assert ibox.side(Side.Top), bigbox.side(Side.Top)) +# assert ibox.side(Side.Right), bigbox.side(Side.Left) - lilsize.x) +# ibox = i2.read().boundbox() +# assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) +# assert ibox.side(Side.Left), bigbox.side(Side.Right) + lilsize.x) +# ibox = i3.read().boundbox() +# assert ibox.side(Side.Left), bigbox.side(Side.Left)) +# assert ibox.side(Side.Top), bigbox.side(Side.Bottom) - lilsize.y) +# ibox = i4.read().boundbox() +# assert ibox.side(Side.Right), bigbox.side(Side.Right)) +# assert ibox.side(Side.Bottom), bigbox.side(Side.Top) + lilsize.y) + +# exports(lib, stack) + + +# def test_place5() -> None : +# # Test separation by units + +# # Get the sample data +# sample_lib = SampleLib.get() +# ibig = sample_lib.ibig +# big = sample_lib.big +# lil = sample_lib.lil +# lib = sample_lib.lib +# parent = sample_lib.parent + +# lib.name = "test_place5" + +# # Relative-placement-adder closure +# add_inst = |inst_name: str, side, sep| { +# i = Instance { +# inst_name: inst_name, +# cell: lil, +# loc: RelativePlace(RelativePlace { +# to: ibig, +# side, +# align: AlignSide(side.ccw_90()), # Leave out `align` as an arg, set one-turn CCW of `side` +# sep, +# }), +# reflect_horiz: False, +# reflect_vert: False, +# } +# parent.add_instance(i) +# } +# # Add a bunch of em +# dx = PrimPitches(Dir.Horiz, 1) +# sep_x = Separation.x(SepBy.UnitSpeced(dx)) +# i1 = add_inst("i1", Side.Left, sep_x) +# i2 = add_inst("i2", Side.Right, sep_x) +# dy = PrimPitches(Dir.Vert, 5) +# sep_y = Separation.y(SepBy.UnitSpeced(dy)) +# i3 = add_inst("i3", Side.Bottom, sep_y) +# i4 = add_inst("i4", Side.Top, sep_y) +# # Add `parent` to the library +# _parent = lib.add_cell(parent) + +# # The real code under test: run placement +# (lib, stack) = Placer.place(lib, SampleStacks.pdka()) + +# # And test the placed results +# bigbox = ibig.read().boundbox() +# ibox = i1.read().boundbox() +# assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) +# assert ibox.side(Side.Right), bigbox.side(Side.Left) - dx) +# ibox = i2.read().boundbox() +# assert ibox.side(Side.Top), bigbox.side(Side.Top)) +# assert ibox.side(Side.Left), bigbox.side(Side.Right) + dx) +# ibox = i3.read().boundbox() +# assert ibox.side(Side.Right), bigbox.side(Side.Right)) +# assert ibox.side(Side.Top), bigbox.side(Side.Bottom) - dy) +# ibox = i4.read().boundbox() +# assert ibox.side(Side.Left), bigbox.side(Side.Left)) +# assert ibox.side(Side.Bottom), bigbox.side(Side.Top) + dy) + +# exports(lib, stack) + + +# def test_place6() -> None : +# # Test port-relative placement + +# # Get the sample data +# sample_lib = SampleLib.get() +# ibig = sample_lib.ibig +# big = sample_lib.big +# lil = sample_lib.lil +# lib = sample_lib.lib +# parent = sample_lib.parent + +# lib.name = "test_place6" + +# # Relative-placement-adder closure +# add_inst = |inst_name: str| { +# i = Instance { +# inst_name: inst_name, +# cell: lil, +# loc: (0, 0), +# reflect_horiz: False, +# reflect_vert: False, +# } +# parent.add_instance(i) +# } +# # Add a `lil` +# i1 = add_inst("i1") + +# # The "code under test": add a relative-placed `Assign`. +# parent.places.append(Placeable.Assign(Ptr(RelAssign { +# net: "NETPPP", +# loc: RelativePlace { +# to: Placeable.Port { +# inst: i1, +# port: "PPP", +# }, +# align: Align.Center, +# side: Side.Left, # FIXME: kinda nonsense +# sep: Separation.z(2), +# }, +# }))) +# # Add `parent` to the library +# parent = lib.add_cell(parent) + +# # The real code under test: run placement +# (lib, stack) = Placer.place(lib, SampleStacks.pdka()) + +# { +# p = parent.read() +# parent_layout = p.layout.as_ref().unwrap() +# assert parent_layout.places.len(), 0) +# assert parent_layout.instances.len(), 2) +# assert parent_layout.cuts.len(), 0) +# assert parent_layout.assignments.len(), 1) +# assn = parent_layout.assignments[0] +# assert assn.net, "NETPPP") +# assert assn.at.track.layer, 2) +# assert assn.at.track.track, 0) +# assert assn.at.cross.layer, 1) +# assert assn.at.cross.track, 1) +# } +# exports(lib, stack) + + +@dataclass +class SampleLib: + lib: Library + big: Cell + ibig: Instance + lil: Cell + parent: Layout + + @classmethod + def get(cls) -> "SampleLib": + """# Get a sample library with test cells `big`, `lil`, and `parent`. + # Designed for adding instances of `lil` relative to `big` all around `parent`.""" + + lib = Library("_rename_me_plz_") + # Create a big center cell + big = Layout("big", 1, Outline.rect(11, 12)) + big = lib.add_cell(big) + # Create the parent cell which instantiates it + parent = Layout("parent", 3, Outline.rect(40, 35)) + # Create an initial instance + ibig = Instance( + name="ibig", + of=big, + loc=AbsPlace.xy(16, 15), + reflect=Reflect.default(), + ) + ibig = parent.add_instance(ibig) + # Create a unit cell which we'll instantiate a few times around `ibig` + lil = Cell("lil") + lil.layout = Layout("lil", 1, Outline.rect(2, 1)) + lil_abs = Abstract("lil", 1, Outline.rect(2, 1)) + lil_abs.ports.append( + Port( + name="PPP", + kind=PortKind.ZTopEdge( + track=0, + side=abs.Side.BottomOrLeft, + into=(2, stack.RelZ.Above), + ), + ) + ) + lil.abs = Some(lil_abs) + lil = lib.add_cell(lil) + return SampleLib( + lib, + big, + ibig, + lil, + parent, + ) diff --git a/Tetris/tests/test_tetris.py b/Tetris/tests/test_tetris.py index 471d1dd..be7f98e 100644 --- a/Tetris/tests/test_tetris.py +++ b/Tetris/tests/test_tetris.py @@ -8,4 +8,3 @@ def test_version(): assert tetris.__version__ == "1.0.0.dev0" - diff --git a/Tetris/tests/test_tetris1.py b/Tetris/tests/test_tetris1.py index b5cfc7f..13d1aa1 100644 --- a/Tetris/tests/test_tetris1.py +++ b/Tetris/tests/test_tetris1.py @@ -2,8 +2,9 @@ # # Unit Tests # +from typing import Optional + # Local imports -from tetris import abstract from tetris.cell import Cell from tetris.instance import Instance from tetris.layout import Layout @@ -17,6 +18,10 @@ # from tetris.validate import ValidStack +def exports(lib: Library, stack: Optional["Stack"] = None) -> None: + ... # FIXME! + + # Create an empty cell def test_empty_cell() -> None: c = Layout( @@ -39,7 +44,12 @@ def test_create_layout() -> None: metals=4, outline=Outline.rect(50, 5), instances=list(), - assignments=[Assign(net="clk", at=TrackCross.from_relz(1, 0, 1, RelZ.Above),)], + assignments=[ + Assign( + net="clk", + at=TrackCross.from_relz(1, 0, 1, RelZ.Above), + ) + ], cuts=list(), ) @@ -52,7 +62,12 @@ def test_create_lib1() -> None: metals=3, outline=Outline.rect(50, 5), instances=list(), - assignments=[Assign(net="clk", at=TrackCross.from_relz(1, 4, 2, RelZ.Below),)], + assignments=[ + Assign( + net="clk", + at=TrackCross.from_relz(1, 4, 2, RelZ.Below), + ) + ], cuts=[ TrackCross.from_relz(0, 1, 1, RelZ.Above), TrackCross.from_relz(0, 1, 3, RelZ.Above), @@ -197,21 +212,20 @@ def test_create_lib1() -> None: # protolib, # resource(format!(":.proto.yaml", protolib.domain)), # ) - + # raw.proto.proto.save( # protolib, # resource(format!(":.proto.bin", protolib.domain)), # ) - + # # Export to GDSII # gds = rawlib.to_gds() # Yaml.save(gds, resource(format!(":.gds.yaml", gds.name))) - + # gds.save(resource(format!(":.gds", gds.name))) # # Grab the full path of resource-file `fname` # def resource(rname: str) -> str : # format!(":/resources/:", env!("CARGO_MANIFEST_DIR"), rname) - diff --git a/Tetris/tetris/__init__.py b/Tetris/tetris/__init__.py index 19dbc4d..b5c001e 100644 --- a/Tetris/tetris/__init__.py +++ b/Tetris/tetris/__init__.py @@ -16,6 +16,7 @@ from .placement import * from .array import * from .group import * +from .instantiable import * # from .layer_period import * # from .tracks import * diff --git a/Tetris/tetris/align.py b/Tetris/tetris/align.py index 2a3a31b..17fee01 100644 --- a/Tetris/tetris/align.py +++ b/Tetris/tetris/align.py @@ -25,4 +25,3 @@ class AlignPorts: # Alignment types Align = Union[AlignSide, AlignCenter, AlignPorts] - diff --git a/Tetris/tetris/array.py b/Tetris/tetris/array.py index 8aa7209..ba7a196 100644 --- a/Tetris/tetris/array.py +++ b/Tetris/tetris/array.py @@ -4,7 +4,7 @@ # Uniformly-spaced repetitions of [Arrayable] elements. # -from pydantic.dataclasses import dataclass +from dataclasses import dataclass # Local imports from .coords import PrimPitches, Xy @@ -15,8 +15,8 @@ @dataclass class Array: - """ # Array - A Uniform-Spaced Array of Identical [`Instantiable`] Elements """ + """# Array + A Uniform-Spaced Array of Identical [`Instantiable`] Elements""" # Array Name name: str @@ -32,4 +32,3 @@ class Array: def boundbox_size(self) -> Xy[PrimPitches]: _unit = self.unit.boundbox_size() raise NotImplementedError # FIXME! - diff --git a/Tetris/tetris/bbox.py b/Tetris/tetris/bbox.py index 3657044..5604512 100644 --- a/Tetris/tetris/bbox.py +++ b/Tetris/tetris/bbox.py @@ -14,26 +14,46 @@ T = TypeVar("T") -# # Bounding Rectangular Box class BoundBox(GenericModel, Generic[T]): - p0: Xy[T] - p1: Xy[T] + """# Rectangular Bounding Box""" + + mins: Xy[T] # Minimum (x,y) coordinates + maxs: Xy[T] # Maximum (x,y) coordinates + + @property + def top(self) -> T: + return self.maxs.y + + @property + def bot(self) -> T: + return self.mins.y + + @property + def bottom(self) -> T: + return self.mins.y + + @property + def left(self) -> T: + return self.mins.x + + @property + def right(self) -> T: + return self.maxs.x - # Retrieve our coordinate at [Side] `side`. def side(self, side: Side) -> T: + """# Retrieve our coordinate at [Side] `side`.""" if side == Side.Left: - return (self.p0.x,) + return self.mins.x if side == Side.Right: - return (self.p1.x,) + return self.maxs.x if side == Side.Bottom: - return (self.p0.y,) + return self.mins.y if side == Side.Top: - return (self.p1.y,) - raise ValueError + return self.maxs.y + raise ValueError(f"Invalid side: {side}") - # Create a new [BoundBox] from potentially unordered pairs of x and y coordinates. def from_xy(xs: Tuple[T, T], ys: Tuple[T, T]) -> "BoundBox": + """# Create a new [BoundBox] from potentially unordered pairs of x and y coordinates.""" (x0, x1) = (xs[0], xs[1]) if xs[0] < xs[1] else (xs[1], xs[0]) (y0, y1) = (ys[0], ys[1]) if ys[0] < ys[1] else (ys[1], ys[0]) return BoundBox(Xy(x0, y0), Xy(x1, y1)) - diff --git a/Tetris/tetris/bundle.py b/Tetris/tetris/bundle.py index 0862236..a5d7b56 100644 --- a/Tetris/tetris/bundle.py +++ b/Tetris/tetris/bundle.py @@ -37,4 +37,3 @@ class Port: class Bundle: name: str ports: List[Port] - diff --git a/Tetris/tetris/cell.py b/Tetris/tetris/cell.py index aa3e997..679ed2a 100644 --- a/Tetris/tetris/cell.py +++ b/Tetris/tetris/cell.py @@ -8,8 +8,7 @@ from typing import List, Set, Dict, Type, Union, Optional - -from pydantic.dataclasses import dataclass +from dataclasses import dataclass # Local imports from .index import Index @@ -39,7 +38,10 @@ class RawLayoutPtr: # # Cell View Enumeration # All of the ways in which a Cell is represented CellView = Union[ - Bundle, Abstract, Layout, RawLayoutPtr, + Bundle, + Abstract, + Layout, + RawLayoutPtr, ] # Collection of the Views describing a Cell @@ -116,4 +118,3 @@ def top_metal(self) -> Optional[Index]: return None else: return Index(metals - 1) - diff --git a/Tetris/tetris/conv/layer_period.py b/Tetris/tetris/conv/layer_period.py index be1c443..2e994d3 100644 --- a/Tetris/tetris/conv/layer_period.py +++ b/Tetris/tetris/conv/layer_period.py @@ -1,7 +1,6 @@ - from typing import List -from pydantic.dataclasses import dataclass +from pydantic.dataclasses import dataclass # Local imports from .coords import DbUnits @@ -9,162 +8,147 @@ from .tracks import Track, TrackData - - @dataclass -class LayerPeriodData : +class LayerPeriodData: signals: List[TrackData] rails: List[TrackData] - # Convert this [Layer]'s track-info into a [LayerPeriodData] def to_layer_period_data(self) -> "LayerPeriodData": period = LayerPeriodData.default() cursor = self.offset - for e in self.entries() : + for e in self.entries(): d = e.width - match e.ttype : - TrackType.Gap => (), - TrackType.Rail(_railkind) => : - period.rails.push(TrackData : - ttype: e.ttype, - index: period.rails.len(), - dir: self.dir, - start: cursor, - width: d, - ) - - TrackType.Signal => : - period.signals.push(TrackData : - ttype: e.ttype, - index: period.signals.len(), - dir: self.dir, - start: cursor, - width: d, - ) - - + # FIXME! + # match e.ttype : + # TrackType.Gap => (), + # TrackType.Rail(_railkind) => : + # period.rails.push(TrackData : + # ttype: e.ttype, + # index: period.rails.len(), + # dir: self.dir, + # start: cursor, + # width: d, + # ) + + # TrackType.Signal => : + # period.signals.push(TrackData : + # ttype: e.ttype, + # index: period.signals.len(), + # dir: self.dir, + # start: cursor, + # width: d, + # ) cursor += d - - Ok(period) - + + return period + # Convert this [Layer]'s track-info into a [LayerPeriod] def to_layer_period( self, index: int, stop: DbUnits, - ) -> LayoutResult : + ) -> LayerPeriod: stop = stop.into() period = LayerPeriod.default() period.index = index cursor = self.offset + (self.pitch() * index) entries = self.entries() - iterator: Box> = - if self.flip == FlipMode.EveryOther index % 2 == 1 : - Box(entries.iter().rev()) - else : - Box(entries.iter()) - - for e in iterator : + if self.flip == FlipMode.EveryOther: + iterator = reversed(entries) + else: + iterator = entries + + for e in iterator: d = e.width - match e.ttype : - TrackType.Gap => (), - TrackType.Rail(railkind) => : - period.rails.push( - Track : - data: TrackData : - ttype: e.ttype, - index: period.rails.len(), - dir: self.dir, - start: cursor, - width: d, - , - segments: vec![TrackSegment : - tp: TrackSegmentType.Rail(railkind), - start: 0.into(), - stop, - ], - - .validate(), - ) - - TrackType.Signal => : - period.signals.push( - Track : - data: TrackData : - ttype: e.ttype, - index: period.signals.len(), - dir: self.dir, - start: cursor, - width: d, - , - segments: vec![TrackSegment : - tp: TrackSegmentType.Wire : src: None , - start: 0.into(), - stop, - ], - - .validate(), - ) - - + # FIXME! + # match e.ttype : + # TrackType.Gap => (), + # TrackType.Rail(railkind) => : + # period.rails.push( + # Track : + # data: TrackData : + # ttype: e.ttype, + # index: period.rails.len(), + # dir: self.dir, + # start: cursor, + # width: d, + # , + # segments: vec![TrackSegment : + # tp: TrackSegmentType.Rail(railkind), + # start: 0.into(), + # stop, + # ], + + # .validate(), + # ) + + # TrackType.Signal => : + # period.signals.push( + # Track : + # data: TrackData : + # ttype: e.ttype, + # index: period.signals.len(), + # dir: self.dir, + # start: cursor, + # width: d, + # , + # segments: vec![TrackSegment : + # tp: TrackSegmentType.Wire : src: None , + # start: 0.into(), + # stop, + # ], + + # .validate(), + # ) cursor += d - - Ok(period) - + + return period + # Transformed single period of [Track]s on a [Layer] # Splits track-info between signals and rails. # Stores each as a [Track] struct, which moves to a (start, width) size-format, # and includes a vector of track-segments for cutting and assigning nets. @dataclass -class LayerPeriod : +class LayerPeriod: index: int signals: List[Track] rails: List[Track] - # Shift the period by `dist` in its periodic direction - def offset(self, dist: DbUnits) -> None : - for t in self.rails : + def offset(self, dist: DbUnits) -> None: + for t in self.rails: t.data.start += dist - - for t in self.signals : + + for t in self.signals: t.data.start += dist - - - + # Set the stop position for all [Track]s to `stop` - def stop(self, stop: DbUnits) -> None : - for t in self.rails : + def stop(self, stop: DbUnits) -> None: + for t in self.rails: t.stop(stop) - - for t in self.signals : + + for t in self.signals: t.stop(stop) - - - + # Cut all [Track]s from `start` to `stop`, def cut( self, start: DbUnits, stop: DbUnits, - src: TrackCross, - ) -> None : - for t in self.rails : + src: TrackCross, + ) -> None: + for t in self.rails: t.cut(start, stop, src) - - for t in self.signals : + + for t in self.signals: t.cut(start, stop, src) - - - + # Block all [Track]s from `start` to `stop`, - def block(self, start: DbUnits, stop: DbUnits, src: "Instance") -> None : - for t in self.rails : + def block(self, start: DbUnits, stop: DbUnits, src: "Instance") -> None: + for t in self.rails: t.block(start, stop, src) - - for t in self.signals : + + for t in self.signals: t.block(start, stop, src) - - \ No newline at end of file diff --git a/Tetris/tetris/conv/tracks.py b/Tetris/tetris/conv/tracks.py index 37d80d9..271218e 100644 --- a/Tetris/tetris/conv/tracks.py +++ b/Tetris/tetris/conv/tracks.py @@ -1,6 +1,6 @@ from enum import Enum, auto -from typing import List, Union, Optionalal +from typing import List, Union, Optional from pydantic.dataclasses import dataclass @@ -32,29 +32,9 @@ class TrackSegment: stop: DbUnits -@dataclass -enum TrackConflict : - Assign(Assign), - Cut(TrackCross), - Blockage(Ptr), - - - def fmt(self, f: std.fmt.Formatter) -> std.fmt.Result : - match self : - # Delegate simpler types to [Debug] - TrackConflict.Assign(a) => std.fmt.Debug.fmt(a, f), - TrackConflict.Cut(c) => std.fmt.Debug.fmt(c, f), - # And for more complicated ones, [Display] - TrackConflict.Blockage(i) => std.fmt.Debug.fmt(i, f), - - +TrackConflict = Union[Assign, TrackCross, Instance] - def from(tp: TrackSegmentType<'_>) -> Self : - match tp : - TrackSegmentType.Cut : src => TrackConflict.Cut(src.clone()), - TrackSegmentType.Blockage : src => TrackConflict.Blockage(src.clone()), - _ => unreachable!(), diff --git a/Tetris/tetris/coords.py b/Tetris/tetris/coords.py index ed0cd81..1ba7e24 100644 --- a/Tetris/tetris/coords.py +++ b/Tetris/tetris/coords.py @@ -13,15 +13,22 @@ class Dir(Enum): - """ Enumerated 2-D Directions """ + """Enumerated 2-D Directions""" Horiz = "horiz" Vert = "vert" + def other(self) -> "Dir": + if self == Dir.Horiz: + return Dir.Vert + if self == Dir.Vert: + return Dir.Horiz + raise ValueError + @dataclass class DbUnits: - """ Distance Specified in Database Units """ + """Distance Specified in Database Units""" num: int @@ -67,7 +74,7 @@ class DbUnits: @dataclass class PrimPitches: - """ Distance in Primitive-Pitches, in Either X/Y Direction """ + """Distance in Primitive-Pitches, in Either X/Y Direction""" dir: Dir num: int @@ -89,6 +96,17 @@ def y(num: int) -> "PrimPitches": def negate(self) -> "PrimPitches": return PrimPitches(self.dir, -self.num) + def __add__(self, other: "PrimPitches") -> "PrimPitches": + if not isinstance(other, PrimPitches): + return NotImplemented + if self.dir != other.dir: + raise ValueError( + "Invalid attempt to add opposite-direction {} and {}".format( + self, other + ) + ) + return PrimPitches(self.dir, self.num + other.num) + # # Numeric operations between primitive-pitch values. # # Generally panic if operating on two [PrimPitches] with different directions. @@ -159,7 +177,7 @@ def negate(self) -> "PrimPitches": @dataclass class LayerPitches: - """ Distance in Pitches on a Particular Layer """ + """Distance in Pitches on a Particular Layer""" layer: Index num: int @@ -205,18 +223,22 @@ class UnitType(Enum): class Xy(GenericModel, Generic[T]): - """ X-Y Cartesian Pair """ + """X-Y Cartesian Pair""" x: T y: T + @staticmethod + def new(x: T, y: T) -> "Xy": + return Xy(x=x, y=y) + def transpose(self) -> "Xy": # Create a new [Xy] with transposed coordinates. Xy(self.y, self.x) def dir(self, dir_: Dir) -> T: - """ Get the dimension in direction `dir` - Also available via square-bracket access through `__getitem__`. """ + """Get the dimension in direction `dir` + Also available via square-bracket access through `__getitem__`.""" if dir_ == Dir.Horiz: return self.x if dir_ == Dir.Vert: @@ -224,7 +246,7 @@ def dir(self, dir_: Dir) -> T: raise ValueError def __getitem__(self, dir_: Dir) -> T: - """ Square bracket access via [Dir] """ + """Square bracket access via [Dir]""" if not isinstance(dir_, Dir): return NotImplemented return self.dir(dir_) diff --git a/Tetris/tetris/error.py b/Tetris/tetris/error.py index 4c3a134..e471fb1 100644 --- a/Tetris/tetris/error.py +++ b/Tetris/tetris/error.py @@ -5,4 +5,3 @@ class LayoutError(Exception): ... # FIXME! Add some real data - diff --git a/Tetris/tetris/group.py b/Tetris/tetris/group.py index 4df8f05..cd1ed6b 100644 --- a/Tetris/tetris/group.py +++ b/Tetris/tetris/group.py @@ -25,4 +25,3 @@ class Group: # Size of the Instance's rectangular `boundbox`, i.e. the zero-origin `boundbox` of its `cell`. def boundbox_size(self) -> Xy[PrimPitches]: raise NotImplementedError - diff --git a/Tetris/tetris/index.py b/Tetris/tetris/index.py index 661a6d9..3469777 100644 --- a/Tetris/tetris/index.py +++ b/Tetris/tetris/index.py @@ -7,4 +7,3 @@ @dataclass class Index: inner: int - diff --git a/Tetris/tetris/instance.py b/Tetris/tetris/instance.py index 1b08074..838e5d4 100644 --- a/Tetris/tetris/instance.py +++ b/Tetris/tetris/instance.py @@ -4,44 +4,22 @@ # Located, oriented instances of other cells or similar reusable layout objects. # -from pydantic.dataclasses import dataclass +from dataclasses import dataclass as std_dataclass # Local imports from .coords import PrimPitches, Xy, Dir from .bbox import BoundBox - +from .reflect import Reflect # from .placement import Place # from .instantiable import Instantiable -@dataclass -class Reflection: - """ Reflection-State of an instance """ - - # Horizontal Reflection State - horiz: bool - # Vertical Reflection State - vert: bool - - def reflected(self, dir_: Dir) -> bool: - """ Boolean indication of whether reflected in direction `dir`. """ - if dir_ == Dir.Horiz: - return self.horiz - if dir_ == Dir.Vert: - return self.vert - raise ValueError - - def __getitem__(self, dir_: Dir) -> bool: - """ Square bracket access. Boolean indication of whether reflected in direction `dir`. """ - return self.reflected(dir_) - - -@dataclass +@std_dataclass class Instance: - """ Instance of another Cell, Group, or Array""" + """Instance of another Cell, Group, or Array""" # Instance Name - inst_name: str + name: str # Target `Cell`, `Group`, `Array`, or other `Instantiable` of: "Instantiable" @@ -51,8 +29,8 @@ class Instance: # If specified in absolute coordinates location-units are [PrimPitches]. loc: "Place" - # Reflection - reflect: Reflection + # Reflect + reflect: Reflect # Boolean indication of whether this Instance is reflected in direction `dir` def reflected(self, dir_: Dir) -> bool: @@ -63,24 +41,26 @@ def boundbox_size(self) -> "Xy[PrimPitches]": return self.of.boundbox_size() def __repr__(self): - return f"Instance(name=:self.inst_name, cell=:self.of.name, loc=:self.loc)" + return f"Instance(name=:self.name, cell=:self.of.name, loc=:self.loc)" - # Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. - # Instance location must be resolved to absolute coordinates, or this method will fail. def boundbox(self) -> BoundBox[PrimPitches]: + """# Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. + # Instance location must be resolved to absolute coordinates, or this method will fail.""" + from .placement import AbsPlace + + if not isinstance(self.loc, AbsPlace): + raise RuntimeError(f"Instance location must be resolved to absolute coordinates") - loc = self.loc.abs() outline = self.of.outline() if self.reflect.horiz: - (x0, x1) = ((loc.x - outline.xmax(), loc.x),) + (x0, x1) = (self.loc.x - outline.xmax(), self.loc.x) else: - (x0, x1) = ((loc.x, loc.x + outline.xmax()),) + (x0, x1) = (self.loc.x, self.loc.x + outline.xmax()) if self.reflect.vert: - (y0, y1) = ((loc.y - outline.ymax(), loc.y),) + (y0, y1) = (self.loc.y - outline.ymax(), self.loc.y) else: - (y0, y1) = ((loc.y, loc.y + outline.ymax()),) - - return BoundBox(Xy(x0, y0), Xy(x1, y1)) + (y0, y1) = (self.loc.y, self.loc.y + outline.ymax()) + return BoundBox(mins=Xy(x=x0, y=y0), maxs=Xy(x=x1, y=y1)) diff --git a/Tetris/tetris/instantiable.py b/Tetris/tetris/instantiable.py index 2233e7c..dbb747b 100644 --- a/Tetris/tetris/instantiable.py +++ b/Tetris/tetris/instantiable.py @@ -7,4 +7,3 @@ # Instantiable Types Union # Primarily used as the `of` target of each `Instance` Instantiable = Union[Cell, Array, Group] - diff --git a/Tetris/tetris/layout.py b/Tetris/tetris/layout.py index 1ecdbc3..fda9ad7 100644 --- a/Tetris/tetris/layout.py +++ b/Tetris/tetris/layout.py @@ -37,9 +37,18 @@ class Layout: # Track cuts cuts: List[TrackCross] = field(default_factory=list) + def add_instance(self, instance: Instance) -> Instance: + self.instances.append(instance) + return instance + # Assign a net at the given coordinates. def assign( - self, net: str, layer: int, track: int, at: int, relz: RelZ, + self, + net: str, + layer: int, + track: int, + at: int, + relz: RelZ, ) -> None: at = TrackCross.from_relz(layer, track, at, relz) self.assignments.append(Assign(net, at)) diff --git a/Tetris/tetris/library.py b/Tetris/tetris/library.py index 51ffe19..7d8798f 100644 --- a/Tetris/tetris/library.py +++ b/Tetris/tetris/library.py @@ -10,16 +10,18 @@ from .cell import Cell -# # Layout Library -# -# A combination of cell definitions, sub-libraries, and metadata -# @dataclass class Library: - # Library Name - name: str - # Cell Definitions + """ + # # Layout Library + # + # A combination of cell definitions, sub-libraries, and metadata + # + """ + + name: str # Library Name cells: Dict[str, Cell] = field(default_factory=dict) + # Cell Definitions # FIXME: `raw` stuff # # [raw.Library] Definitions @@ -29,11 +31,16 @@ class Library: # conv.raw.RawExporter.convert(self, stack) # # Add a [raw.Library] # def add_rawlib(self, rawlib: raw.Library) -> Ptr : - # self.rawlibs.insert(rawlib) + # self.rawlibs.add(rawlib) # Add a [Cell] - def add_cell(self, cell: Cell) -> None: - self.cells.append(cell) + def add_cell(self, cell: Cell) -> Cell: + if not isinstance(cell, Cell): + raise TypeError + if cell.name in self.cells: + raise RuntimeError + self.cells[cell.name] = cell + return cell # Create an ordered list in which dependent cells follow their dependencies. def dep_order(self) -> List[Cell]: @@ -41,35 +48,34 @@ def dep_order(self) -> List[Cell]: # # Dependency-Orderer -# # Creates an ordered list in which dependent cells follow their dependencies. -# FIXME: migrate to utils.DepOrder -# @dataclass class DepOrder: lib: Library - stack: List[Cell] - seen: Set[Cell] # FIXME! hashing of these aint gonna happen + order: List[Cell] = field(default_factory=list) + done: Set[str] = field(default_factory=set) + pending: Set[str] = field(default_factory=set) def order(lib: Library) -> List[Cell]: - myself = DepOrder(lib=lib, stack=list(), seen=set()) - for cell in myself.lib.cells.keys(): - myself.push(cell) - return myself.stack + myself = DepOrder(lib=lib, order=[], done=set(), pending=set()) + for cell in lib.cells.values(): + myself.process(cell) + return myself.order - def push(self, cellname: str): + def process(self, cell: Cell): # If the Cell hasn't already been visited, depth-first search it - if cellname in self.seen: + if cell.name in self.done: return # Already done + if cell.name in self.pending: + raise RuntimeError(f"Cycle in cell dependencies: {cell.name}") + self.pending.add(cell.name) - # Read the cell-pointer - cell = self.lib.cells[cellname] # If the cell has an implementation, visit its [Instance]s before inserting it if cell.layout is not None: for inst in cell.layout.instances: - self.push(inst.cell.name) + self.process(inst.of) # And insert the cell (pointer) itself - self.seen.insert(cellname) - self.stack.push(cell) - + self.pending.remove(cell.name) + self.done.add(cell.name) + self.order.append(cell) diff --git a/Tetris/tetris/outline.py b/Tetris/tetris/outline.py index df4ef48..fe28aef 100644 --- a/Tetris/tetris/outline.py +++ b/Tetris/tetris/outline.py @@ -37,13 +37,13 @@ class Outline: # Outline constructor from primitive-pitches def from_prim_pitches(x: List[PrimPitches], y: List[PrimPitches]) -> "Outline": # Check that x and y are of compatible lengths - if x.len() < 1 or x.len() != y.len(): + if len(x) < 1 or len(x) != len(y): raise LayoutError("Invalid zero-length Outline dimensions") # Check for: # * Correct directions # * all non-negative values - for k in 0.0 .x.len(): + for k in range(len(x)): if x[k].dir != Dir.Horiz or y[k].dir != Dir.Vert: raise LayoutError("Invalid Outline direction(s)") @@ -53,7 +53,7 @@ def from_prim_pitches(x: List[PrimPitches], y: List[PrimPitches]) -> "Outline": # Check for: # * x non-increasing-ness, # * y for non-decreasing-ness - for k in 1.0 .x.len(): + for k in range(len(x), 1): if x[k].num > x[k - 1].num: raise LayoutError("Invalid Outline with non-increasing x-coordinates") @@ -64,18 +64,18 @@ def from_prim_pitches(x: List[PrimPitches], y: List[PrimPitches]) -> "Outline": @staticmethod def rect(x: int, y: int) -> "Outline": - """ Create a new rectangular outline of dimenions `x` by `y`""" + """Create a new rectangular outline of dimenions `x` by `y`""" return Outline([PrimPitches(Dir.Horiz, x)], [PrimPitches(Dir.Vert, y)]) # Maximum x-coordinate # (Which is also always the *first* x-coordinate) def xmax(self) -> PrimPitches: - self.x[0] + return self.x[0] # Maximum y-coordinate # (Which is also always the *last* y-coordinate) def ymax(self) -> PrimPitches: - self.y[self.y.len() - 1] + return self.y[len(self.y) - 1] # Maximum coordinate in [Dir] `dir` def max(self, dir_: Dir) -> PrimPitches: diff --git a/Tetris/tetris/placement.py b/Tetris/tetris/placement.py index ea6dabb..491f2e0 100644 --- a/Tetris/tetris/placement.py +++ b/Tetris/tetris/placement.py @@ -2,7 +2,7 @@ # # Layout21 Placement Module # -from typing import Union +from typing import Union, Any, Optional from pydantic.dataclasses import dataclass @@ -24,7 +24,7 @@ class RelAssign: @dataclass class Port: - """ Reference to the Location of a Port """ + """Reference to the Location of a Port""" inst: Instance port: str @@ -43,11 +43,32 @@ def loc(self: Placeable) -> "Place": raise TypeError + + +@dataclass +class AbsPlace: + """Absolute-Valued Placement, in Primitive Pitches""" + + xy: Xy ## FIXME: [PrimPitches] + + @property + def x(self) -> PrimPitches: + return self.xy.x + + @property + def y(self) -> PrimPitches: + return self.xy.y + + @staticmethod + def xy(x: int, y: int) -> "AbsPlace": + return AbsPlace(xy=Xy(x=PrimPitches.x(x), y=PrimPitches.y(y))) + + # # Relative Placement @dataclass class RelativePlace: # Placement is relative `to` this - to: Placeable + to: Any ## FIXME: Placeable # Placed on this `side` of `to` side: Side # Aligned to this aspect of `to` @@ -55,13 +76,7 @@ class RelativePlace: # Separation between the placement and the `to` sep: Separation - -@dataclass -class AbsPlace: - """ Absolute-Valued Placement, in Primitive Pitches """ - - xy: Xy[PrimPitches] - + resolved: Optional[AbsPlace] = None # # Placement Union # diff --git a/Tetris/tetris/placer.py b/Tetris/tetris/placer.py new file mode 100644 index 0000000..2f1a5f0 --- /dev/null +++ b/Tetris/tetris/placer.py @@ -0,0 +1,357 @@ +""" +# Tetris Placer + +Converts potentially relatively-placed attributes to absolute positions. +""" + +# Std-Lib Imports +from typing import List, Tuple, Union, Optional, Set +from dataclasses import field + +# PyPi Imports +from dataclasses import dataclass + +# Local imports +from .cell import Cell +from .instance import Instance +from .reflect import Reflect +from .bbox import BoundBox +from .layout import Layout +from .coords import Dir, PrimPitches, UnitSpeced, Xy +from .library import Library +from .placement import Placeable, RelativePlace, AbsPlace, Side +from .separation import SepBy +from .stack import ValidStack +from .abstract import Abstract +from .align import Align, AlignSide +from .library import Library +from .stack import ValidStack + +StackEntry = Union[Library, Cell, Layout, Abstract, Instance] # FIXME: maybe more + +# # Placer +# Converts all potentially-relatively-placed attributes to absolute positions. +@dataclass +class Placer: + lib: Library + stack: Optional[ValidStack] + ctx: List[StackEntry] + + # [Placer] public API entrypoint. + # Modify and return [Library] `lib`, converting all [RelativePlace]s to absolute locations. + @classmethod + def place(cls, lib: Library, stack: ValidStack) -> Tuple[Library, ValidStack]: + this = cls( + lib, + stack, + ctx=[], + ) + this.place_lib() + return (this.lib, this.stack) + + # Primary internal implementation method. Update placements for [Library] `self.lib`. + def place_lib(self) -> None: + self.ctx.append(self.lib) + # Iterate over all the library's cells, updating their instance-placements. + for cell in self.lib.dep_order(): + self.ctx.append(cell) + if cell.layout is not None: + self.place_layout(cell.layout) + self.ctx.pop() + self.ctx.pop() + + # Update placements for [Layout] `layout` + def place_layout(self, layout: Layout) -> None: + self.ctx.append(layout) + + # Move `instances` and `places` into one vector of [Placeable]s + places: List[Placeable] = layout.instances + # places.extend(layout.places) + layout.places = [] + + # Iterate over `places` in dependency order, updating any relative-places to absolute. + ordered = PlaceOrder.get_ordered(places) + for place in ordered: + if isinstance(place, Instance): + inst = place + if isinstance(inst.loc, RelativePlace): + # Convert to an absolute location + inst.loc.resolved = self.resolve_instance_place(inst) + + # Add the now-absolute-placed inst to the `instances` list + # FIXME: do this or nah? + # layout.instances.append(inst_ptr.clone()) + + # if isinstance(place, Array): + # array_inst = ptr.write() + # if Place.Rel(ref rel) = array_inst.loc { + # # Convert to an absolute location + # abs = self.resolve_array_place(*array_inst, rel) + # array_inst.loc = Place.Abs(abs) + # } + # # And flatten its instances + # children = self.flatten_array_inst(*array_inst) + # children = children.into_iter().map(|i| Ptr.new(i)) + # layout.instances.extend(children) + # } + # Placeable.Assign(ref ptr) => { + # assn = ptr.read() + # abs: TrackCross = self.resolve_assign_place(assn.loc) + # new_assn = stack.Assign { + # net: assn.net.clone(), + # at: abs, + # } + # layout.assignments.append(new_assn) + # } + # Placeable.Group(_) => unimplemented!(), + # Placeable.Port { .. } => (), # Nothing to do, at least until hitting something that *depends* on the Port location + + self.ctx.pop() + + # Flatten an [ArrayInstance] to a vector of Cell Instances. + # Instance location must be absolute by call-time. + # def flatten_array_inst( + # self, + # array_inst: ArrayInstance, + # ) -> LayoutResult> { + # # Read the child-Instances from the underlying [Array] definition + # children = { + # array = array_inst.array.read() + # self.flatten_array(*array, array_inst.name) + # } + # # Get its initial location + # loc = array_inst.loc.abs() + # # Translate each child to our location and reflection + # for child in children.iter_mut() { + # childloc = child.loc.abs_mut() + # if array_inst.reflect_horiz { + # # Reflect horizontally + # childloc.x *= -1_isize + # child.reflect_horiz = !child.reflect_horiz + # } + # if array_inst.reflect_vert { + # # Reflect vertically + # childloc.y *= -1_isize + # child.reflect_vert = !child.reflect_vert + # } + # # Translate its location + # *childloc += *loc + + # Flatten an [Array] to a vector of Cell Instances. + # def flatten_array(self, array: Array, prefix: str) -> LayoutResult> { + # insts = Vec.with_capacity(array.count) + + # # Get the separations in each dimension + # xsep = match array.sep.x { + # None => PrimPitches.x(0), + # Some(SepBy.UnitSpeced(u)) => { + # match u { + # UnitSpeced.PrimPitches(p) => p.clone(), + # _ => unimplemented!(), # TODO: other units + # } + # } + # Some(SepBy.SizeOf(_)) => unimplemented!(), + # } + # ysep = match array.sep.y { + # None => PrimPitches.y(0), + # Some(SepBy.UnitSpeced(u)) => { + # match u { + # UnitSpeced.PrimPitches(p) => p.clone(), + # _ => unimplemented!(), # TODO: other units + # } + # } + # Some(SepBy.SizeOf(_)) => unimplemented!(), + # } + # sep = Xy.new(xsep, ysep) + + # # Initialize our location to the array-origin + # loc = (0, 0).into() + # for i in 0..array.count { + # match array.unit { + # Arrayable.Instance(cell) => { + # i = Instance { + # inst_name: format!("{}[{}]", prefix, i), # `arrayname[i]` + # cell: cell.clone(), + # loc: Place.Abs(loc), + # reflect_horiz: false, + # reflect_vert: false, + # } + # insts.append(i) + # } + # Arrayable.Array(arr) => { + # # Create a new [ArrayInstance] at the current location, + # # largely for sake of reusing `flatten_array_inst` + # # to get its located, flattened children. + # i = ArrayInstance { + # name: format!("{}[{}]", prefix, i), # `arrayname[i]` + # array: arr.clone(), + # loc: Place.Abs(loc), + # reflect_horiz: false, + # reflect_vert: false, + # } + # # (Potentially recursively) flatten that short-lived [ArrayInstance] + # children = self.flatten_array_inst(i) + # # And add its children to ours + # insts.extend(children) + # } + # Arrayable.Group(_arr) => unimplemented!(), + # } + # # Increment the location by our (two-dimensional) increment. + # loc += sep + + # return insts + + # Resolve a location of [ArrayInstance] `inst` relative to its [RelativePlace] `rel`. + # def resolve_array_place( + # self, + # _inst: ArrayInstance, + # _rel: RelativePlace, + # ) -> LayoutResult> { + # # FIXME: this should just need the same stuff as `resolve_instance_place`, + # # once we have a consolidated version of [Instance] that covers Arrays. + # todo!() + # } + def resolve_instance_place(self, inst: Instance) -> AbsPlace: + """# Resolve a location of [Instance] `inst` relative to its [RelativePlace] `rel`.""" + self.ctx.append(inst) + + # Get the relative-to instance's bounding box + if not isinstance(inst.loc, RelativePlace): + raise RuntimeError("Expected RelativePlace") + place_relative_to_this_bbox = inst.loc.to.boundbox() + + # The coordinate axes here are referred to as `side`, corresponding to `inst.loc.side`, and `align`, corresponding to `inst.loc.align`. + # Mapping these back to (x,y) happens at the very end. + # FIXME: checks that `side` and `align` are orthogonal should come earlier + + # Collect the two sides of the placed instance dictated by `inst.loc.to` + left = right = top = bottom = None + + if inst.loc.side == Side.Left: + right = place_relative_to_this_bbox.left + elif inst.loc.side == Side.Right: + left = place_relative_to_this_bbox.right + elif inst.loc.side == Side.Top: + bottom = place_relative_to_this_bbox.top + elif inst.loc.side == Side.Bottom: + top = place_relative_to_this_bbox.bottom + else: + raise ValueError + + if inst.loc.align.side == Side.Left: + left = place_relative_to_this_bbox.left + elif inst.loc.align.side == Side.Right: + right = place_relative_to_this_bbox.right + elif inst.loc.align.side == Side.Top: + top = place_relative_to_this_bbox.top + elif inst.loc.align.side == Side.Bottom: + bottom = place_relative_to_this_bbox.bottom + else: + raise ValueError + + origin = something(inst=inst, top=top, bottom=bottom, left=left, right=right) + return AbsPlace(origin) + + +def something( + inst: Instance, + top: Optional[PrimPitches], + bottom: Optional[PrimPitches], + left: Optional[PrimPitches], + right: Optional[PrimPitches], +)-> Xy: + # What we know at this point: + # * The cell's bounding box + # * *Either* the top or bottom edge of the instance + # * *Either* the right or left edge of the instance + # * The instance's reflection state + # Now find its origin + ... + cell_size = inst.boundbox_size() + + if top is None: + top = bottom + cell_size.y + if bottom is None: + bottom = top - cell_size.y + if left is None: + left = right - cell_size.x + if right is None: + right = left + cell_size.x + + bbox = BoundBox(mins=Xy(x=left, y=bottom), maxs=Xy(x=right, y=top)) + return get_origin_something(bbox, inst.reflect) + + +def get_origin_something(bbox: BoundBox, reflect: Reflect) -> Xy: + """# Get the origin of an object with bounding box `bbox` and reflection-state `reflect`.""" + x = bbox.right if reflect.horiz else bbox.left + y = bbox.top if reflect.vert else bbox.bottom + return Xy(x=x, y=y) + + +@dataclass +class PlaceOrder: + placeables: List[Placeable] + order: List[Placeable] = field(default_factory=list) + pending: Set[str] = field(default_factory=set) + done: Set[str] = field(default_factory=set) + + @classmethod + def get_ordered(cls, placeables: List[Placeable]) -> List[Placeable]: + order = cls(placeables) + for p in placeables: + order.push(p) + return order.order + + def push(self, item: Placeable): + # Depth-first search dependent Instance placements + if item.name in self.done: + return # Already processed + + # Check for cycles, indicated if `item` is in the pending-set, i.e. an open recursive stack-frame. + if item.name in self.pending: + self.fail() + + self.pending.add(item.name) + # Process the Item, dependencies first + self.process(item) + # Check that `item` hasn't (somehow) been removed from the pending-set + self.pending.remove(item.name) + + # And insert the Item itself + self.done.add(item.name) + self.order.append(item) + + def process(self, item: Placeable) -> None: + # Process [Instance]-pointer `item` + if isinstance(item, Instance): + if isinstance(item.loc, RelativePlace): + self.push(item.loc.to) # Visit the dependency first + elif isinstance(item.loc, AbsPlace): + return # Nothing to do + else: + raise TypeError(f"Unexpected location type {item.loc}") + else: + raise TypeError # FIXME! + + # Placeable.Array(ref p) => { + # if Place.Rel(rel) = p.read().loc { + # self.order.append(rel.to) # Visit the dependency first + # } + # } + # Placeable.Group(ref p) => { + # if Place.Rel(rel) = p.read().loc { + # self.order.append(rel.to) # Visit the dependency first + # } + # } + # Placeable.Assign(a) => { + # # Always relative, append it unconditionally + # a = a.read() + # self.order.append(a.loc.to) + # } + # Placeable.Port { ref inst, .. } => { + # if Place.Rel(rel) = inst.read().loc { + # self.order.append(rel.to) # Visit the dependency first + + def fail(self, msg: Optional[str] = None): + raise RuntimeError(msg or "") diff --git a/Tetris/tetris/placer.rs b/Tetris/tetris/placer.rs deleted file mode 100644 index 2b6d7ba..0000000 --- a/Tetris/tetris/placer.rs +++ /dev/null @@ -1,1000 +0,0 @@ -//! -//! # Tetris Placer -//! -//! Converts potentially relatively-placed attributes to absolute positions. -//! - -// Std-Lib Imports -use std::convert::TryFrom; - -// Local imports -use crate::bbox::HasBoundBox; -use crate::{instance::Instance, layout::Layout}; -use crate::coords::{LayerPitches, PrimPitches, UnitSpeced, Xy}; -use crate::library::Library; -use crate::array::{Array, ArrayInstance, Arrayable}; -use crate::placement::{ - Align, Place, Placeable, RelativePlace, SepBy, Side, -}; -use crate::raw::{Dir, LayoutError, LayoutResult}; -use crate::utils::{DepOrder, DepOrderer, ErrorContext, ErrorHelper, Ptr}; -use crate::validate::ValidStack; -use crate::{ - abs, stack, - tracks::{TrackCross, TrackRef}, -}; - -/// # Placer -/// Converts all potentially-relatively-placed attributes to absolute positions. -pub struct Placer { - lib: Library, - stack: ValidStack, - ctx: Vec, -} -impl Placer { - /// [Placer] public API entrypoint. - /// Modify and return [Library] `lib`, converting all [RelativePlace]s to absolute locations. - pub fn place(lib: Library, stack: ValidStack) -> LayoutResult<(Library, ValidStack)> { - let mut this = Self { - lib, - stack, - ctx: Vec::new(), - }; - this.place_lib()?; - Ok((this.lib, this.stack)) - } - /// Primary internal implementation method. Update placements for [Library] `self.lib`. - fn place_lib(&mut self) -> LayoutResult<()> { - self.ctx.push(ErrorContext::Library(self.lib.name.clone())); - // Iterate over all the library's cells, updating their instance-placements. - for cellptr in &self.lib.dep_order() { - let mut cell = cellptr.write()?; - self.ctx.push(ErrorContext::Cell(cell.name.clone())); - if let Some(ref mut layout) = cell.layout { - self.place_layout(layout)?; - } - self.ctx.pop(); - } - self.ctx.pop(); - Ok(()) - } - /// Update placements for [Layout] `layout` - fn place_layout(&mut self, layout: &mut Layout) -> LayoutResult<()> { - self.ctx.push(ErrorContext::Impl); - - // Move `instances` and `places` into one vector of [Placeable]s - let mut places: Vec = layout - .instances - .drain(..) - .map(|i| Placeable::Instance(i)) - .collect(); - places.extend(layout.places.drain(..)); - - // Iterate over `places` in dependency order, updating any relative-places to absolute. - let mut ordered = PlaceOrder::order(&places)?; - for place in ordered.drain(..) { - match place { - Placeable::Instance(ref inst_ptr) => { - let mut inst = inst_ptr.write()?; - if let Place::Rel(ref rel) = inst.loc { - // Convert to an absolute location - let abs = self.resolve_instance_place(&*inst, rel)?; - inst.loc = Place::Abs(abs); - } - // Add the now-absolute-placed inst to the `instances` list - layout.instances.push(inst_ptr.clone()); - } - Placeable::Array(ref ptr) => { - let mut array_inst = ptr.write()?; - if let Place::Rel(ref rel) = array_inst.loc { - // Convert to an absolute location - let abs = self.resolve_array_place(&*array_inst, rel)?; - array_inst.loc = Place::Abs(abs); - } - // And flatten its instances - let children = self.flatten_array_inst(&*array_inst)?; - let children = children.into_iter().map(|i| Ptr::new(i)); - layout.instances.extend(children); - } - Placeable::Assign(ref ptr) => { - let assn = ptr.read()?; - let abs: TrackCross = self.resolve_assign_place(&assn.loc)?; - let new_assn = stack::Assign { - net: assn.net.clone(), - at: abs, - }; - layout.assignments.push(new_assn); - } - Placeable::Group(_) => unimplemented!(), - Placeable::Port { .. } => (), // Nothing to do, at least until hitting something that *depends* on the Port location - } - } - self.ctx.pop(); - Ok(()) - } - /// Flatten an [ArrayInstance] to a vector of Cell Instances. - /// Instance location must be absolute by call-time. - fn flatten_array_inst( - &mut self, - array_inst: &ArrayInstance, - ) -> LayoutResult> { - // Read the child-Instances from the underlying [Array] definition - let mut children = { - let array = array_inst.array.read()?; - self.flatten_array(&*array, &array_inst.name)? - }; - // Get its initial location - let loc = array_inst.loc.abs()?; - // Translate each child to our location and reflection - for child in children.iter_mut() { - let childloc = child.loc.abs_mut()?; - if array_inst.reflect_horiz { - // Reflect horizontally - childloc.x *= -1_isize; - child.reflect_horiz = !child.reflect_horiz; - } - if array_inst.reflect_vert { - // Reflect vertically - childloc.y *= -1_isize; - child.reflect_vert = !child.reflect_vert; - } - // Translate its location - *childloc += *loc; - } - Ok(children) - } - /// Flatten an [Array] to a vector of Cell Instances. - fn flatten_array(&mut self, array: &Array, prefix: &str) -> LayoutResult> { - let mut insts = Vec::with_capacity(array.count); - - // Get the separations in each dimension - let xsep = match array.sep.x { - None => PrimPitches::x(0), - Some(SepBy::UnitSpeced(u)) => { - match u { - UnitSpeced::PrimPitches(p) => p.clone(), - _ => unimplemented!(), // TODO: other units - } - } - Some(SepBy::SizeOf(_)) => unimplemented!(), - }; - let ysep = match array.sep.y { - None => PrimPitches::y(0), - Some(SepBy::UnitSpeced(u)) => { - match u { - UnitSpeced::PrimPitches(p) => p.clone(), - _ => unimplemented!(), // TODO: other units - } - } - Some(SepBy::SizeOf(_)) => unimplemented!(), - }; - let sep = Xy::new(xsep, ysep); - - // Initialize our location to the array-origin - let mut loc = (0, 0).into(); - for i in 0..array.count { - match &array.unit { - Arrayable::Instance(cell) => { - let i = Instance { - inst_name: format!("{}[{}]", prefix, i), // `arrayname[i]` - cell: cell.clone(), - loc: Place::Abs(loc), - reflect_horiz: false, - reflect_vert: false, - }; - insts.push(i); - } - Arrayable::Array(arr) => { - // Create a new [ArrayInstance] at the current location, - // largely for sake of reusing `flatten_array_inst` - // to get its located, flattened children. - let i = ArrayInstance { - name: format!("{}[{}]", prefix, i), // `arrayname[i]` - array: arr.clone(), - loc: Place::Abs(loc), - reflect_horiz: false, - reflect_vert: false, - }; - // (Potentially recursively) flatten that short-lived [ArrayInstance] - let children = self.flatten_array_inst(&i)?; - // And add its children to ours - insts.extend(children); - } - Arrayable::Group(_arr) => unimplemented!(), - }; - // Increment the location by our (two-dimensional) increment. - loc += sep; - } - Ok(insts) - } - /// Resolve a location of [ArrayInstance] `inst` relative to its [RelativePlace] `rel`. - fn resolve_array_place( - &mut self, - _inst: &ArrayInstance, - _rel: &RelativePlace, - ) -> LayoutResult> { - // FIXME: this should just need the same stuff as `resolve_instance_place`, - // once we have a consolidated version of [Instance] that covers Arrays. - todo!() - } - /// Resolve a location of [Instance] `inst` relative to its [RelativePlace] `rel`. - fn resolve_instance_place( - &mut self, - inst: &Instance, - rel: &RelativePlace, - ) -> LayoutResult> { - self.ctx - .push(ErrorContext::Instance(inst.inst_name.clone())); - - // Get the relative-to instance's bounding box - let bbox = match rel.to { - Placeable::Instance(ref ptr) => ptr.read()?.boundbox()?, - Placeable::Array(ref ptr) => ptr.read()?.boundbox()?, - Placeable::Group(_) => unimplemented!(), - Placeable::Assign(_) => unimplemented!(), - Placeable::Port { .. } => unimplemented!(), - }; - // The coordinate axes here are referred to as `side`, corresponding to `rel.side`, and `align`, corresponding to `rel.align`. - // Mapping these back to (x,y) happens at the very end. - // FIXME: checks that `side` and `align` are orthogonal should come earlier - - // Get its edge-coordinates in each axis - let mut side_coord = bbox.side(rel.side); - let align_side = match rel.align { - Align::Side(s) => s, - _ => unimplemented!(), - }; - let mut align_coord = bbox.side(align_side); - let side_axis = match rel.side { - Side::Left | Side::Right => Dir::Horiz, - Side::Top | Side::Bottom => Dir::Vert, - }; - let align_axis = side_axis.other(); - // Sort out whether the instance needs a reflection-based offset in each axis - let offset_side = match rel.side { - Side::Left | Side::Bottom => !inst.reflected(side_axis), - Side::Top | Side::Right => inst.reflected(side_axis), - }; - let offset_align = match align_side { - Side::Left | Side::Bottom => inst.reflected(align_axis), - Side::Top | Side::Right => !inst.reflected(align_axis), - }; - // Add in any reflection-based offsets - if offset_side || offset_align { - let inst_size = inst.boundbox_size()?; - if offset_side { - if inst.reflected(side_axis) { - side_coord = side_coord + inst_size[side_axis]; - } else { - side_coord = side_coord - inst_size[side_axis]; - } - } - if offset_align { - if inst.reflected(align_axis) { - align_coord = align_coord + inst_size[align_axis]; - } else { - align_coord = align_coord - inst_size[align_axis]; - } - } - } - // Add in our separation - if rel.sep.z.is_some() { - self.fail("Z-axis separation is invalid for Instances")?; - } - if rel.sep.dir(align_axis).is_some() { - self.fail("Separation in the alignment-axis is invalid for Instances")?; - } - // Get the side-axis separation - let sep_side_axis = match &rel.sep.dir(side_axis) { - None => PrimPitches::new(side_axis, 0), - Some(SepBy::SizeOf(cellptr)) => { - let cell = cellptr.read()?; - cell.boundbox_size()?[side_axis] - } - Some(SepBy::UnitSpeced(ref u)) => { - match u { - UnitSpeced::DbUnits(_) => self.fail("Invalid separation units: DbUnits")?, - UnitSpeced::LayerPitches(_) => { - // Do a buncha coordinate transformations - todo!() - } - UnitSpeced::PrimPitches(ref p) => { - if p.dir != side_axis { - self.fail(format!("Separation {:?} specified in invalid axis", u))?; - } - p.clone() - } - } - } - }; - // Invert the separation if necessary - let sep_side_axis = match &rel.side { - Side::Top | Side::Right => sep_side_axis, - Side::Left | Side::Bottom => sep_side_axis.negate(), - }; - // And finally add it in - side_coord = side_coord + sep_side_axis; - // Move back to (x,y) coordinates - let res = match rel.side { - Side::Left | Side::Right => Xy::new(side_coord, align_coord), - Side::Top | Side::Bottom => Xy::new(align_coord, side_coord), - }; - self.ctx.pop(); - Ok(res) - } - /// Resolve the location of a track-crossing at `rel` - fn resolve_assign_place(&mut self, rel: &RelativePlace) -> LayoutResult { - let port_loc = match &rel.to { - Placeable::Port { inst, port } => self.locate_instance_port(&*inst.read()?, port)?, - Placeable::Instance(_) => unimplemented!(), - Placeable::Array(_) => unimplemented!(), - Placeable::Group(_) => unimplemented!(), - Placeable::Assign(_) => unimplemented!(), - }; - let ref_cross: (TrackRef, TrackRef) = match port_loc { - PortLoc::ZTopEdge { track, range } => { - let ortho_track = match rel.align { - Align::Side(s) => { - match s { - Side::Bottom | Side::Left => range.0.track, // FIXME: reflection support - Side::Top | Side::Right => range.1.track, // FIXME: reflection support - } - } - Align::Center => (range.0.track + range.1.track) / 2, - Align::Ports(_, _) => unreachable!(), - }; - ( - track.clone(), - TrackRef { - layer: range.0.layer, - track: ortho_track, - }, - ) - } - _ => unimplemented!(), - }; - - // Sort out separation (in tracks) in (x, y) - let _sep_x = match &rel.sep.x { - Some(_) => unimplemented!(), - None => 0_usize, - }; - let _sep_y = match &rel.sep.y { - Some(_) => unimplemented!(), - None => 0_usize, - }; - // Sort out the layer-based z-separation - let sep_z = match &rel.sep.z { - Some(i) => *i, - None => 0, - }; - let newlayer = isize::try_from(ref_cross.0.layer)? + sep_z; - let newlayer = usize::try_from(newlayer)?; - - // Of the two layers in `ref_cross`, one will be in parallel with `newlayer`, and one will be orthogonal to it. - // Set `par` as the parallel track, and `cross` as the orthogonal track. - let (par, cross) = { - let newlayer_dir = self.stack.metal(newlayer)?.spec.dir; - if newlayer_dir == self.stack.metal(ref_cross.0.layer)?.spec.dir { - (ref_cross.0, ref_cross.1) - } else if newlayer_dir == self.stack.metal(ref_cross.1.layer)?.spec.dir { - (ref_cross.1, ref_cross.0) - } else { - return self.fail("Invalid non-crossing TrackCross"); - } - }; - - // Get the track on `newlayer` closest to `par` - let new_track: TrackRef = self.convert_track_layer(&par, newlayer)?; - // And turn the combination into our result [TrackCross] - let rv = TrackCross::new(new_track, cross); - Ok(rv) - } - /// Resolve a location of [Instance] `inst` relative to its [RelativePlace] `rel`. - fn locate_instance_port(&mut self, inst: &Instance, portname: &str) -> LayoutResult { - // Port locations are only valid for Cells with Abs definitions. Otherwise fail. - let cell = inst.cell.read()?; // Note `cell` is alive for the duration of this function - let abs = self.unwrap( - cell.abs.as_ref(), - format!( - "Cannot Location Port {} on Cell {} with no Abs View", - portname, cell.name - ), - )?; - // Get the Port-object, or fail - let port = self.unwrap( - abs.port(portname), - format!("Cell {} has no Port {}", cell.name, portname), - )?; - - let loc = match &port.kind { - abs::PortKind::Edge { .. /*layer, track, side*/ } => unimplemented!(), - abs::PortKind::ZTopEdge { track, side, into } => { - let top_metal = self.unwrap(cell.top_metal()?, "No metal layers")?; - let (dir, nsignals) = { - // Get relevant data from our [Layer], and quickly drop a reference to it. - let layer = self.stack.metal(top_metal)?; - (layer.spec.dir, layer.period_data.signals.len()) - }; - let port_track = { - let layer_pitches = - self.layer_pitches(top_metal, inst.loc.abs()?[!dir].into())?; - - // Get the port's track-index, combining in the instance-location - let (_, period_tracks) = (layer_pitches * nsignals).into_inner(); - let period_tracks = usize::try_from(period_tracks)?; - if inst.reflected(!dir) { - period_tracks - track - 1 - } else { - period_tracks + track - } - }; - - // Sort out the orthogonal-axis range. - let ortho_layer = match into.1 { - stack::RelZ::Above => top_metal + 1, - stack::RelZ::Below => top_metal - 1, - }; - let ortho_range = { - let layer = &self.stack.metal(ortho_layer)?; - let nsignals = layer.period_data.signals.len(); - - // Find the origin and size in `ortho_layer` tracks - // FIXME: probably make this a method, and/or a track-index `HasUnits` - let loc = inst.loc.abs()?[dir]; - let loc = self.layer_pitches(ortho_layer, loc.into())?; - let (_, loc) = (loc * nsignals).into_inner(); - let loc = usize::try_from(loc)?; - let size = inst.boundbox_size()?[dir]; - let size = self.layer_pitches(ortho_layer, size.into())?; - let (_, size) = (size * nsignals).into_inner(); - let size = usize::try_from(size)?; - - // Pull out the number of tracks in the orthogonal-axis - let into = into.0; - // And sort out the orthogonal range, based on location, size, and `into` tracks - match (side, inst.reflected(dir)) { - (abs::Side::BottomOrLeft, false)=> (loc, loc+into), - (abs::Side::BottomOrLeft, true)=> (loc-into, loc), - (abs::Side::TopOrRight, false)=> (loc+into, loc+size), - (abs::Side::TopOrRight, true)=> (loc-size, loc-into), - } - }; - - // From all that, create a [PortLoc] - PortLoc::ZTopEdge { - track: TrackRef { - layer: top_metal, - track: port_track, - }, - range: ( - TrackRef { - layer: ortho_layer, - track: ortho_range.0, - }, - TrackRef { - layer: ortho_layer, - track: ortho_range.1 - }, - ), - } - } - abs::PortKind::ZTopInner { .. } => unimplemented!(), - }; - Ok(loc) - } - /// Convert a [TrackRef] to the closest track on another same-direction layer `to_layer`. - fn convert_track_layer( - &mut self, - trackref: &TrackRef, - to_layer: usize, - ) -> LayoutResult { - if trackref.layer == to_layer { - // Same layer, no conversion needed - return Ok(trackref.clone()); - } - let track_layer_dir = &self.stack.metal(trackref.layer)?.spec.dir; - let to_layer_dir = &self.stack.metal(to_layer)?.spec.dir; - if track_layer_dir != to_layer_dir { - // Orthogonal layers. Fail. - self.fail(format!( - "Cannot convert between tracks on {:?} layer {} and {:?} layer {}", - track_layer_dir, trackref.layer, to_layer_dir, to_layer - ))?; - } - // Normal case - actually do some work. - // First find the starting-track's center in [DbUnits] - let track_center = self.stack.metal(trackref.layer)?.center(trackref.track)?; - // And get the corresponding track-index on the other layer - let to_layer_index = self.stack.metal(to_layer)?.track_index(track_center)?; - Ok(TrackRef { - layer: to_layer, - track: to_layer_index, - }) - } - /// Convert a [UnitSpeced] distance `dist` into [LayerPitches] on layer `layer_index`. - /// Fails if `dist` is not an integer multiple of the pitch of `layer_index`. - fn layer_pitches( - &mut self, - layer_index: usize, - dist: UnitSpeced, - ) -> LayoutResult { - let layer = &self.stack.metal(layer_index)?; - let layer_pitch = layer.pitch; - let num = match dist { - UnitSpeced::DbUnits(_) => unimplemented!(), - UnitSpeced::LayerPitches(_) => unimplemented!(), - UnitSpeced::PrimPitches(p) => { - let dir = layer.spec.dir; - let prim_pitch = self.stack.prim.pitches[dir.other()]; - if layer_pitch % prim_pitch != 0 { - self.fail(format!( - "Invalid Conversion: Primitive (pitch={:?}) to Layer {} (pitch={:?})", - prim_pitch, layer_index, layer_pitch - ))?; - } - p.num * (layer_pitch / prim_pitch) - } - }; - Ok(LayerPitches::new(layer_index, num)) - } -} - -/// Resolved Locations corresponding to [abs::PortKind]s -#[derive(Debug, Clone)] -pub enum PortLoc { - Edge { - /// Port Track - track: TrackRef, - /// Crossing Track - at: TrackRef, - }, - ZTopEdge { - /// Port Track - track: TrackRef, - /// Extent on an adjacent layer - range: (TrackRef, TrackRef), - }, - ZTopInner { - /// Locations - locs: Vec, - }, -} -#[derive(Debug, Clone)] -pub struct Tbd; -impl ErrorHelper for Placer { - type Error = LayoutError; - fn err(&self, msg: impl Into) -> LayoutError { - LayoutError::Export { - message: msg.into(), - stack: self.ctx.clone(), - } - } -} - -/// Empty struct for implementing the [DepOrder] trait for relative-placed [Instance]s. -struct PlaceOrder; -impl DepOrder for PlaceOrder { - type Item = Placeable; - type Error = LayoutError; - - /// Process [Instance]-pointer `item` - fn process(item: &Placeable, orderer: &mut DepOrderer) -> LayoutResult<()> { - match item { - Placeable::Instance(ref p) => { - let inst = p.read()?; - if let Place::Rel(rel) = &inst.loc { - orderer.push(&rel.to)?; // Visit the dependency first - } - } - Placeable::Array(ref p) => { - if let Place::Rel(rel) = &p.read()?.loc { - orderer.push(&rel.to)?; // Visit the dependency first - } - } - Placeable::Group(ref p) => { - if let Place::Rel(rel) = &p.read()?.loc { - orderer.push(&rel.to)?; // Visit the dependency first - } - } - Placeable::Assign(a) => { - // Always relative, push it unconditionally - let a = a.read()?; - orderer.push(&a.loc.to)?; - } - Placeable::Port { ref inst, .. } => { - if let Place::Rel(rel) = &inst.read()?.loc { - orderer.push(&rel.to)?; // Visit the dependency first - } - } - }; - Ok(()) - } - fn fail() -> Result<(), Self::Error> { - Err(LayoutError::msg("Placement ordering error")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::outline::Outline; - use crate::cell::Cell; - use crate::placement::{Place, Placeable, RelAssign, RelativePlace, SepBy, Separation, Side}; - use crate::tests::{exports, stacks::SampleStacks}; - - #[test] - fn test_place1() -> LayoutResult<()> { - // Most basic smoke-test - Placer::place(Library::new("plib"), SampleStacks::empty()?)?; - Ok(()) - } - #[test] - fn test_place2() -> LayoutResult<()> { - // Initial test of relative placement - - let mut lib = Library::new("test_place2"); - // Create a unit cell which we'll instantiate a few times - let unit = Layout::new("unit", 0, Outline::rect(3, 7)?); - let unit = lib.cells.add(unit); - // Create an initial instance - let i0 = Instance { - inst_name: "i0".into(), - cell: unit.clone(), - loc: (47, 51).into(), - reflect_horiz: false, - reflect_vert: false, - }; - // Create the parent cell which instantiates it - let mut parent = Layout::new("parent", 0, Outline::rect(100, 100)?); - let i0 = parent.instances.add(i0); - // Create another Instance, placed relative to `i0` - let i1 = Instance { - inst_name: "i1".into(), - cell: unit.clone(), - loc: Place::Rel(RelativePlace { - to: Placeable::Instance(i0.clone()), - side: Side::Right, - align: Align::Side(Side::Bottom), - sep: Separation::default(), - }), - reflect_horiz: false, - reflect_vert: false, - }; - let i1 = parent.instances.add(i1); - let parent = lib.cells.add(parent); - - // The real code-under-test: run placement - let (lib, stack) = Placer::place(lib, SampleStacks::empty()?)?; - - // Checks on results - assert_eq!(lib.cells.len(), 2); - // Note these next two checks do *pointer* equality, not value equality - assert_eq!(lib.cells[0], unit); - assert_eq!(lib.cells[1], parent); - // Now check the locations - { - let inst = i0.read()?; - assert_eq!(*inst.loc.abs()?, Xy::::from((47, 51))); - } - { - let inst = i1.read()?; - assert_eq!(*inst.loc.abs()?, Xy::::from((50, 51))); - } - exports(lib, stack) - } - #[test] - fn test_place3() -> LayoutResult<()> { - // Test each relative side and alignment - - // Get the sample data - let SampleLib { - ibig, - // big, - lil, - mut lib, - mut parent, - .. - } = SampleLib::get()?; - lib.name = "test_place3".into(); - let relto = Placeable::Instance(ibig.clone()); - - // Relative-placement-adder closure - let mut add_inst = |inst_name: &str, side, align| { - let i = Instance { - inst_name: inst_name.into(), - cell: lil.clone(), - loc: Place::Rel(RelativePlace { - to: relto.clone(), - side, - align: Align::Side(align), - sep: Separation::default(), - }), - reflect_horiz: false, - reflect_vert: false, - }; - parent.instances.add(i) - }; - // Add a bunch of em - let i1 = add_inst("i1", Side::Left, Side::Bottom); - let i2 = add_inst("i2", Side::Right, Side::Bottom); - let i3 = add_inst("i3", Side::Bottom, Side::Left); - let i4 = add_inst("i4", Side::Bottom, Side::Right); - let i5 = add_inst("i5", Side::Left, Side::Top); - let i6 = add_inst("i6", Side::Right, Side::Top); - let i7 = add_inst("i7", Side::Top, Side::Left); - let i8 = add_inst("i8", Side::Top, Side::Right); - - // Add `parent` to the library - let _parent = lib.cells.add(parent); - - // The real code under test: run placement - let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; - - // And test the placed results - let bigbox = ibig.read()?.boundbox()?; - let ibox = i1.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left)); - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); - let ibox = i2.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right)); - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); - let ibox = i3.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom)); - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); - let ibox = i4.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom)); - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); - let ibox = i5.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left)); - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); - let ibox = i6.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right)); - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); - let ibox = i7.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top)); - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); - let ibox = i8.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top)); - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); - exports(lib, stack) - } - #[test] - fn test_place4() -> LayoutResult<()> { - // Test size-of separation - - // Get the sample data - let SampleLib { - ibig, - // big, - lil, - mut lib, - mut parent, - .. - } = SampleLib::get()?; - lib.name = "test_place4".into(); - - // Relative-placement-adder closure - let mut add_inst = |inst_name: &str, side, sep| { - let i = Instance { - inst_name: inst_name.into(), - cell: lil.clone(), - loc: Place::Rel(RelativePlace { - to: Placeable::Instance(ibig.clone()), - side, - align: Align::Side(side.cw_90()), // Leave out `align` as an arg, set one-turn CW of `side` - sep, - }), - reflect_horiz: false, - reflect_vert: false, - }; - parent.instances.add(i) - }; - // Add a bunch of em - let sep_x = Separation::x(SepBy::SizeOf(lil.clone())); - let i1 = add_inst("i1", Side::Left, sep_x.clone()); - let i2 = add_inst("i2", Side::Right, sep_x.clone()); - let sep_y = Separation::y(SepBy::SizeOf(lil.clone())); - let i3 = add_inst("i3", Side::Bottom, sep_y.clone()); - let i4 = add_inst("i4", Side::Top, sep_y.clone()); - // Add `parent` to the library - let _parent = lib.cells.add(parent); - - // The real code under test: run placement - let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; - - // And test the placed results - let lilsize = lil.read()?.boundbox_size()?; - let bigbox = ibig.read()?.boundbox()?; - let ibox = i1.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left) - lilsize.x); - let ibox = i2.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right) + lilsize.x); - let ibox = i3.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom) - lilsize.y); - let ibox = i4.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top) + lilsize.y); - - exports(lib, stack) - } - #[test] - fn test_place5() -> LayoutResult<()> { - // Test separation by units - - // Get the sample data - let SampleLib { - ibig, - // big, - lil, - mut lib, - mut parent, - .. - } = SampleLib::get()?; - lib.name = "test_place5".into(); - - // Relative-placement-adder closure - let mut add_inst = |inst_name: &str, side, sep| { - let i = Instance { - inst_name: inst_name.into(), - cell: lil.clone(), - loc: Place::Rel(RelativePlace { - to: Placeable::Instance(ibig.clone()), - side, - align: Align::Side(side.ccw_90()), // Leave out `align` as an arg, set one-turn CCW of `side` - sep, - }), - reflect_horiz: false, - reflect_vert: false, - }; - parent.instances.add(i) - }; - // Add a bunch of em - let dx = PrimPitches::new(Dir::Horiz, 1); - let sep_x = Separation::x(SepBy::UnitSpeced(dx.clone().into())); - let i1 = add_inst("i1", Side::Left, sep_x.clone()); - let i2 = add_inst("i2", Side::Right, sep_x.clone()); - let dy = PrimPitches::new(Dir::Vert, 5); - let sep_y = Separation::y(SepBy::UnitSpeced(dy.clone().into())); - let i3 = add_inst("i3", Side::Bottom, sep_y.clone()); - let i4 = add_inst("i4", Side::Top, sep_y.clone()); - // Add `parent` to the library - let _parent = lib.cells.add(parent); - - // The real code under test: run placement - let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; - - // And test the placed results - let bigbox = ibig.read()?.boundbox()?; - let ibox = i1.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Bottom)); - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Left) - dx); - let ibox = i2.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Top)); - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Right) + dx); - let ibox = i3.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Right), bigbox.side(Side::Right)); - assert_eq!(ibox.side(Side::Top), bigbox.side(Side::Bottom) - dy); - let ibox = i4.read()?.boundbox()?; - assert_eq!(ibox.side(Side::Left), bigbox.side(Side::Left)); - assert_eq!(ibox.side(Side::Bottom), bigbox.side(Side::Top) + dy); - - exports(lib, stack) - } - #[test] - fn test_place6() -> LayoutResult<()> { - // Test port-relative placement - - // Get the sample data - let SampleLib { - // ibig, - // big, - lil, - mut lib, - mut parent, - .. - } = SampleLib::get()?; - lib.name = "test_place6".into(); - - // Relative-placement-adder closure - let mut add_inst = |inst_name: &str| { - let i = Instance { - inst_name: inst_name.into(), - cell: lil.clone(), - loc: (0, 0).into(), - reflect_horiz: false, - reflect_vert: false, - }; - parent.instances.add(i) - }; - // Add a `lil` - let i1 = add_inst("i1"); - - // The "code under test": add a relative-placed `Assign`. - parent.places.push(Placeable::Assign(Ptr::new(RelAssign { - net: "NETPPP".into(), - loc: RelativePlace { - to: Placeable::Port { - inst: i1.clone(), - port: "PPP".into(), - }, - align: Align::Center, - side: Side::Left, // FIXME: kinda nonsense - sep: Separation::z(2), - }, - }))); - // Add `parent` to the library - let parent = lib.cells.add(parent); - - // The real code under test: run placement - let (lib, stack) = Placer::place(lib, SampleStacks::pdka()?)?; - - { - let p = parent.read()?; - let parent_layout = p.layout.as_ref().unwrap(); - assert_eq!(parent_layout.places.len(), 0); - assert_eq!(parent_layout.instances.len(), 2); - assert_eq!(parent_layout.cuts.len(), 0); - assert_eq!(parent_layout.assignments.len(), 1); - let assn = &parent_layout.assignments[0]; - assert_eq!(assn.net, "NETPPP"); - assert_eq!(assn.at.track.layer, 2); - assert_eq!(assn.at.track.track, 0); - assert_eq!(assn.at.cross.layer, 1); - assert_eq!(assn.at.cross.track, 1); - } - exports(lib, stack) - } - pub struct SampleLib { - pub lib: Library, - pub big: Ptr, - pub ibig: Ptr, - pub lil: Ptr, - pub parent: Layout, - } - impl SampleLib { - /// Get a sample library with test cells `big`, `lil`, and `parent`. - /// Designed for adding instances of `lil` relative to `big` all around `parent`. - pub fn get() -> LayoutResult { - let mut lib = Library::new("_rename_me_plz_"); - // Create a big center cell - let big = Layout::new("big", 1, Outline::rect(11, 12)?); - let big = lib.cells.add(big); - // Create the parent cell which instantiates it - let mut parent = Layout::new("parent", 3, Outline::rect(40, 35)?); - // Create an initial instance - let ibig = Instance { - inst_name: "ibig".into(), - cell: big.clone(), - loc: (16, 15).into(), - reflect_horiz: false, - reflect_vert: false, - }; - let ibig = parent.instances.add(ibig); - // Create a unit cell which we'll instantiate a few times around `ibig` - let mut lil = Cell::new("lil"); - lil.layout = Some(Layout::new("lil", 1, Outline::rect(2, 1)?)); - let mut lil_abs = abs::Abstract::new("lil", 1, Outline::rect(2, 1)?); - lil_abs.ports.push(abs::Port { - name: "PPP".into(), - kind: abs::PortKind::ZTopEdge { - track: 0, - side: abs::Side::BottomOrLeft, - into: (2, stack::RelZ::Above), - }, - }); - lil.abs = Some(lil_abs); - let lil = lib.cells.add(lil); - Ok(SampleLib { - lib, - big, - ibig, - lil, - parent, - }) - } - } -} diff --git a/Tetris/tetris/reflect.py b/Tetris/tetris/reflect.py new file mode 100644 index 0000000..c442e08 --- /dev/null +++ b/Tetris/tetris/reflect.py @@ -0,0 +1,30 @@ +from pydantic.dataclasses import dataclass + +# Local imports +from .coords import PrimPitches, Xy, Dir +from .bbox import BoundBox + + +@dataclass +class Reflect: + """# Reflection-State""" + + horiz: bool # Reflected horizontally + vert: bool # Reflected vertically + + @staticmethod + def default() -> "Reflect": + """The default, non-reflected state.""" + return Reflect(horiz=False, vert=False) + + def reflected(self, dir_: Dir) -> bool: + """Boolean indication of whether reflected in direction `dir`.""" + if dir_ == Dir.Horiz: + return self.horiz + if dir_ == Dir.Vert: + return self.vert + raise ValueError(f"Invalid direction: {dir_}") + + def __getitem__(self, dir_: Dir) -> bool: + """Square bracket access. Boolean indication of whether reflected in direction `dir`.""" + return self.reflected(dir_) diff --git a/Tetris/tetris/relz.py b/Tetris/tetris/relz.py index 557ece7..5801989 100644 --- a/Tetris/tetris/relz.py +++ b/Tetris/tetris/relz.py @@ -2,7 +2,7 @@ class RelZ(Enum): - """ Relative Z-Axis Reference to one Layer `Above` or `Below` another """ + """Relative Z-Axis Reference to one Layer `Above` or `Below` another""" Above = auto() Below = auto() @@ -13,4 +13,3 @@ def other(self) -> "RelZ": if self == RelZ.Below: return RelZ.Above raise ValueError - diff --git a/Tetris/tetris/separation.py b/Tetris/tetris/separation.py index a30b7ec..9c6ea67 100644 --- a/Tetris/tetris/separation.py +++ b/Tetris/tetris/separation.py @@ -28,6 +28,10 @@ class Separation: y: Optional[SepBy] = None z: Optional[int] = None + @staticmethod + def zero() -> "Separation": + return Separation() + @staticmethod def by_x(x: SepBy) -> "Separation": return Separation(x=x) @@ -47,4 +51,3 @@ def dir(self, dir_: Dir) -> Optional[SepBy]: if dir_ == Dir.Vert: return self.y raise ValueError - diff --git a/Tetris/tetris/side.py b/Tetris/tetris/side.py index 3e7abaf..720289c 100644 --- a/Tetris/tetris/side.py +++ b/Tetris/tetris/side.py @@ -30,4 +30,3 @@ def ccw_90(self) -> "Side": if self == Side.Right: return Side.Top raise ValueError - diff --git a/Tetris/tetris/stack.py b/Tetris/tetris/stack.py index 27025fc..a3b6cb1 100644 --- a/Tetris/tetris/stack.py +++ b/Tetris/tetris/stack.py @@ -143,3 +143,7 @@ def validate(self) -> "ValidStack": from .validate import validate_stack return validate_stack(self) + + +# FIXME: these will probably be the same type +ValidStack = Stack diff --git a/Tetris/tetris/track_spec.py b/Tetris/tetris/track_spec.py index 9a20940..6e9121a 100644 --- a/Tetris/tetris/track_spec.py +++ b/Tetris/tetris/track_spec.py @@ -33,14 +33,18 @@ class TrackCross: # Create from four [int], representing the two (layer-index, track-index) pairs. def from_parts(layer1: int, index1: int, layer2: int, index2: int) -> "TrackCross": return TrackCross( - track=TrackRef(layer1, index1), cross=TrackRef(layer2, index2), + track=TrackRef(layer1, index1), + cross=TrackRef(layer2, index2), ) # Create from a (layer-index, track-index) pair and a [RelZ] def from_relz(layer: int, track: int, at: int, relz: RelZ) -> "TrackCross": layer2 = layer + 1 if relz == RelZ.Above else layer - 1 track = TrackRef(layer, track) - cross = TrackRef(layer=layer2, track=at,) + cross = TrackRef( + layer=layer2, + track=at, + ) return TrackCross(track, cross) diff --git a/Tetris/tetris/units.py b/Tetris/tetris/units.py index b3090d4..376c79d 100644 --- a/Tetris/tetris/units.py +++ b/Tetris/tetris/units.py @@ -4,4 +4,3 @@ @dataclass class Units: ... # FIXME! - From 43faae0440d5db3b0e0cd26bbcb839e1d4606a94 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Wed, 25 Jan 2023 13:32:15 -0800 Subject: [PATCH 20/28] Another Placer Test --- .vscode/settings.json | 7 + Tetris/tests/test_placer.py | 289 ++++++++++++++---------------------- Tetris/tetris/__init__.py | 3 + Tetris/tetris/coords.py | 11 ++ Tetris/tetris/instance.py | 32 ++-- Tetris/tetris/placement.py | 4 + Tetris/tetris/separation.py | 10 +- 7 files changed, 165 insertions(+), 191 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e7f1a97 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "Tetris" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/Tetris/tests/test_placer.py b/Tetris/tests/test_placer.py index 45c6fc8..d2f22a7 100644 --- a/Tetris/tests/test_placer.py +++ b/Tetris/tests/test_placer.py @@ -2,17 +2,18 @@ from typing import Optional import tetris -from tetris.align import Align, AlignSide -from tetris.abstract import Abstract, Port, PortKind -from tetris.library import Library -from tetris.instance import Instance, Reflect -from tetris.layout import Layout +from tetris import Align, AlignSide +from tetris import Abstract, Port, PortKind +from tetris import Library +from tetris import Instance, Reflect +from tetris import Layout +from tetris import Outline +from tetris import Cell +from tetris import Place, Placeable, RelAssign, RelativePlace, Side, AbsPlace +from tetris import SepBy, Separation + +# Non-public imports from tetris.placer import Placer -from tetris.outline import Outline -from tetris.cell import Cell -from tetris.placement import Place, Placeable, RelAssign, RelativePlace, Side, AbsPlace -from tetris.separation import SepBy, Separation - # from .tests import exports, SampleStacks @@ -83,79 +84,78 @@ def test_place2() -> None: exports(lib, stack) -# def test_place3() -> None : -# # Test each relative side and alignment - -# # Get the sample data -# sample_lib = SampleLib.get() -# ibig = sample_lib.ibig -# big = sample_lib.big -# lil = sample_lib.lil -# lib = sample_lib.lib -# parent = sample_lib.parent - -# lib.name = "test_place3" -# relto = ibig - -# # Relative-placement-adder closure -# add_inst = |inst_name: str, side, align| { -# i = Instance( -# inst_name: inst_name, -# cell: lil, -# loc: RelativePlace(RelativePlace( -# to: relto, -# side, -# align: AlignSide(align), -# sep: Separation.zero(), -# ), -# reflect_horiz: False, -# reflect_vert: False, -# ) -# parent.add_instance(i) -# } -# # Add a bunch of em -# i1 = add_inst("i1", Side.Left, Side.Bottom) -# i2 = add_inst("i2", Side.Right, Side.Bottom) -# i3 = add_inst("i3", Side.Bottom, Side.Left) -# i4 = add_inst("i4", Side.Bottom, Side.Right) -# i5 = add_inst("i5", Side.Left, Side.Top) -# i6 = add_inst("i6", Side.Right, Side.Top) -# i7 = add_inst("i7", Side.Top, Side.Left) -# i8 = add_inst("i8", Side.Top, Side.Right) - -# # Add `parent` to the library -# _parent = lib.add_cell(parent) - -# # The real code under test: run placement -# (lib, stack) = Placer.place(lib, SampleStacks.pdka()) - -# # And test the placed results -# bigbox = ibig.read().boundbox() -# ibox = i1.read().boundbox() -# assert ibox.side(Side.Right), bigbox.side(Side.Left)) -# assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) -# ibox = i2.read().boundbox() -# assert ibox.side(Side.Left), bigbox.side(Side.Right)) -# assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) -# ibox = i3.read().boundbox() -# assert ibox.side(Side.Top), bigbox.side(Side.Bottom)) -# assert ibox.side(Side.Left), bigbox.side(Side.Left)) -# ibox = i4.read().boundbox() -# assert ibox.side(Side.Top), bigbox.side(Side.Bottom)) -# assert ibox.side(Side.Right), bigbox.side(Side.Right)) -# ibox = i5.read().boundbox() -# assert ibox.side(Side.Right), bigbox.side(Side.Left)) -# assert ibox.side(Side.Top), bigbox.side(Side.Top)) -# ibox = i6.read().boundbox() -# assert ibox.side(Side.Left), bigbox.side(Side.Right)) -# assert ibox.side(Side.Top), bigbox.side(Side.Top)) -# ibox = i7.read().boundbox() -# assert ibox.side(Side.Bottom), bigbox.side(Side.Top)) -# assert ibox.side(Side.Left), bigbox.side(Side.Left)) -# ibox = i8.read().boundbox() -# assert ibox.side(Side.Bottom), bigbox.side(Side.Top)) -# assert ibox.side(Side.Right), bigbox.side(Side.Right)) -# exports(lib, stack) +def test_place3() -> None: + # Test each relative side and alignment + + # Get the sample data + sample_lib = SampleLib.get() + ibig = sample_lib.ibig + big = sample_lib.big + lil = sample_lib.lil + lib = sample_lib.lib + parent = sample_lib.parent + + lib.name = "test_place3" + relto = ibig + + # Relative-placement-adder closure + def add_inst(name: str, side: Side, align: Side) -> Instance: + i = Instance( + name=name, + of=lil, + loc=RelativePlace( + to=relto, + side=side, + align=AlignSide(align), + sep=Separation.zero(), + ), + reflect=Reflect.default(), + ) + return parent.add_instance(i) + + # Add a bunch of em + i1 = add_inst("i1", Side.Left, Side.Bottom) + i2 = add_inst("i2", Side.Right, Side.Bottom) + i3 = add_inst("i3", Side.Bottom, Side.Left) + i4 = add_inst("i4", Side.Bottom, Side.Right) + i5 = add_inst("i5", Side.Left, Side.Top) + i6 = add_inst("i6", Side.Right, Side.Top) + i7 = add_inst("i7", Side.Top, Side.Left) + i8 = add_inst("i8", Side.Top, Side.Right) + + # Add `parent` to the library + parent = lib.add_cell(Cell(name="parent", layout=parent)) + + # The real code under test: run placement + (lib, stack) = Placer.place(lib, SampleStacks.pdka()) + + # And test the placed results + bigbox = ibig.boundbox() + ibox = i1.boundbox() + assert ibox.side(Side.Right) == bigbox.side(Side.Left) + assert ibox.side(Side.Bottom) == bigbox.side(Side.Bottom) + ibox = i2.boundbox() + assert ibox.side(Side.Left) == bigbox.side(Side.Right) + assert ibox.side(Side.Bottom) == bigbox.side(Side.Bottom) + ibox = i3.boundbox() + assert ibox.side(Side.Top) == bigbox.side(Side.Bottom) + assert ibox.side(Side.Left) == bigbox.side(Side.Left) + ibox = i4.boundbox() + assert ibox.side(Side.Top) == bigbox.side(Side.Bottom) + assert ibox.side(Side.Right) == bigbox.side(Side.Right) + ibox = i5.boundbox() + assert ibox.side(Side.Right) == bigbox.side(Side.Left) + assert ibox.side(Side.Top) == bigbox.side(Side.Top) + ibox = i6.boundbox() + assert ibox.side(Side.Left) == bigbox.side(Side.Right) + assert ibox.side(Side.Top) == bigbox.side(Side.Top) + ibox = i7.boundbox() + assert ibox.side(Side.Bottom) == bigbox.side(Side.Top) + assert ibox.side(Side.Left) == bigbox.side(Side.Left) + ibox = i8.boundbox() + assert ibox.side(Side.Bottom) == bigbox.side(Side.Top) + assert ibox.side(Side.Right) == bigbox.side(Side.Right) + exports(lib, stack) # def test_place4() -> None : @@ -172,9 +172,9 @@ def test_place2() -> None: # lib.name = "test_place4" # # Relative-placement-adder closure -# add_inst = |inst_name: str, side, sep| { +# add_inst = |name: str, side, sep| { # i = Instance( -# inst_name: inst_name, +# name: name, # cell: lil, # loc: RelativePlace(RelativePlace( # to: ibig, @@ -201,18 +201,18 @@ def test_place2() -> None: # (lib, stack) = Placer.place(lib, SampleStacks.pdka()) # # And test the placed results -# lilsize = lil.read().boundbox_size() -# bigbox = ibig.read().boundbox() -# ibox = i1.read().boundbox() +# lilsize = lil.boundbox_size() +# bigbox = ibig.boundbox() +# ibox = i1.boundbox() # assert ibox.side(Side.Top), bigbox.side(Side.Top)) # assert ibox.side(Side.Right), bigbox.side(Side.Left) - lilsize.x) -# ibox = i2.read().boundbox() +# ibox = i2.boundbox() # assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) # assert ibox.side(Side.Left), bigbox.side(Side.Right) + lilsize.x) -# ibox = i3.read().boundbox() +# ibox = i3.boundbox() # assert ibox.side(Side.Left), bigbox.side(Side.Left)) # assert ibox.side(Side.Top), bigbox.side(Side.Bottom) - lilsize.y) -# ibox = i4.read().boundbox() +# ibox = i4.boundbox() # assert ibox.side(Side.Right), bigbox.side(Side.Right)) # assert ibox.side(Side.Bottom), bigbox.side(Side.Top) + lilsize.y) @@ -233,9 +233,9 @@ def test_place2() -> None: # lib.name = "test_place5" # # Relative-placement-adder closure -# add_inst = |inst_name: str, side, sep| { +# add_inst = |name: str, side, sep| { # i = Instance { -# inst_name: inst_name, +# name: name, # cell: lil, # loc: RelativePlace(RelativePlace { # to: ibig, @@ -264,88 +264,28 @@ def test_place2() -> None: # (lib, stack) = Placer.place(lib, SampleStacks.pdka()) # # And test the placed results -# bigbox = ibig.read().boundbox() -# ibox = i1.read().boundbox() +# bigbox = ibig.boundbox() +# ibox = i1.boundbox() # assert ibox.side(Side.Bottom), bigbox.side(Side.Bottom)) # assert ibox.side(Side.Right), bigbox.side(Side.Left) - dx) -# ibox = i2.read().boundbox() +# ibox = i2.boundbox() # assert ibox.side(Side.Top), bigbox.side(Side.Top)) # assert ibox.side(Side.Left), bigbox.side(Side.Right) + dx) -# ibox = i3.read().boundbox() +# ibox = i3.boundbox() # assert ibox.side(Side.Right), bigbox.side(Side.Right)) # assert ibox.side(Side.Top), bigbox.side(Side.Bottom) - dy) -# ibox = i4.read().boundbox() +# ibox = i4.boundbox() # assert ibox.side(Side.Left), bigbox.side(Side.Left)) # assert ibox.side(Side.Bottom), bigbox.side(Side.Top) + dy) # exports(lib, stack) -# def test_place6() -> None : -# # Test port-relative placement - -# # Get the sample data -# sample_lib = SampleLib.get() -# ibig = sample_lib.ibig -# big = sample_lib.big -# lil = sample_lib.lil -# lib = sample_lib.lib -# parent = sample_lib.parent - -# lib.name = "test_place6" - -# # Relative-placement-adder closure -# add_inst = |inst_name: str| { -# i = Instance { -# inst_name: inst_name, -# cell: lil, -# loc: (0, 0), -# reflect_horiz: False, -# reflect_vert: False, -# } -# parent.add_instance(i) -# } -# # Add a `lil` -# i1 = add_inst("i1") - -# # The "code under test": add a relative-placed `Assign`. -# parent.places.append(Placeable.Assign(Ptr(RelAssign { -# net: "NETPPP", -# loc: RelativePlace { -# to: Placeable.Port { -# inst: i1, -# port: "PPP", -# }, -# align: Align.Center, -# side: Side.Left, # FIXME: kinda nonsense -# sep: Separation.z(2), -# }, -# }))) -# # Add `parent` to the library -# parent = lib.add_cell(parent) - -# # The real code under test: run placement -# (lib, stack) = Placer.place(lib, SampleStacks.pdka()) - -# { -# p = parent.read() -# parent_layout = p.layout.as_ref().unwrap() -# assert parent_layout.places.len(), 0) -# assert parent_layout.instances.len(), 2) -# assert parent_layout.cuts.len(), 0) -# assert parent_layout.assignments.len(), 1) -# assn = parent_layout.assignments[0] -# assert assn.net, "NETPPP") -# assert assn.at.track.layer, 2) -# assert assn.at.track.track, 0) -# assert assn.at.cross.layer, 1) -# assert assn.at.cross.track, 1) -# } -# exports(lib, stack) - - @dataclass class SampleLib: + # The sample library used in several tests, + # including attribute-references to key instances and cells. + lib: Library big: Cell ibig: Instance @@ -360,7 +300,7 @@ def get(cls) -> "SampleLib": lib = Library("_rename_me_plz_") # Create a big center cell big = Layout("big", 1, Outline.rect(11, 12)) - big = lib.add_cell(big) + big = lib.add_cell(Cell(name="big", layout=big)) # Create the parent cell which instantiates it parent = Layout("parent", 3, Outline.rect(40, 35)) # Create an initial instance @@ -374,19 +314,20 @@ def get(cls) -> "SampleLib": # Create a unit cell which we'll instantiate a few times around `ibig` lil = Cell("lil") lil.layout = Layout("lil", 1, Outline.rect(2, 1)) - lil_abs = Abstract("lil", 1, Outline.rect(2, 1)) - lil_abs.ports.append( - Port( - name="PPP", - kind=PortKind.ZTopEdge( - track=0, - side=abs.Side.BottomOrLeft, - into=(2, stack.RelZ.Above), - ), - ) - ) - lil.abs = Some(lil_abs) + lil.abs = Abstract(name="lil", metals=1, outline=Outline.rect(2, 1), ports=[]) + # lil_abs.ports.append( + # Port( + # name="PPP", + # kind=PortKind.ZTopEdge( + # track=0, + # side=abs.Side.BottomOrLeft, + # into=(2, stack.RelZ.Above), + # ), + # ) + # ) + # lil.abs = Some(lil_abs) lil = lib.add_cell(lil) + return SampleLib( lib, big, diff --git a/Tetris/tetris/__init__.py b/Tetris/tetris/__init__.py index b5c001e..ecd4cd9 100644 --- a/Tetris/tetris/__init__.py +++ b/Tetris/tetris/__init__.py @@ -17,6 +17,9 @@ from .array import * from .group import * from .instantiable import * +from .reflect import * +from .align import * +from .separation import * # from .layer_period import * # from .tracks import * diff --git a/Tetris/tetris/coords.py b/Tetris/tetris/coords.py index 1ba7e24..24db87c 100644 --- a/Tetris/tetris/coords.py +++ b/Tetris/tetris/coords.py @@ -107,6 +107,17 @@ def __add__(self, other: "PrimPitches") -> "PrimPitches": ) return PrimPitches(self.dir, self.num + other.num) + def __sub__(self, other: "PrimPitches") -> "PrimPitches": + if not isinstance(other, PrimPitches): + return NotImplemented + if self.dir != other.dir: + raise ValueError( + "Invalid attempt to add opposite-direction {} and {}".format( + self, other + ) + ) + return PrimPitches(self.dir, self.num - other.num) + # # Numeric operations between primitive-pitch values. # # Generally panic if operating on two [PrimPitches] with different directions. diff --git a/Tetris/tetris/instance.py b/Tetris/tetris/instance.py index 838e5d4..70522c8 100644 --- a/Tetris/tetris/instance.py +++ b/Tetris/tetris/instance.py @@ -4,17 +4,18 @@ # Located, oriented instances of other cells or similar reusable layout objects. # -from dataclasses import dataclass as std_dataclass +from dataclasses import dataclass, field # Local imports from .coords import PrimPitches, Xy, Dir from .bbox import BoundBox from .reflect import Reflect + # from .placement import Place # from .instantiable import Instantiable -@std_dataclass +@dataclass # Note this is a std-lib dataclass, not pydantic class Instance: """Instance of another Cell, Group, or Array""" @@ -27,10 +28,10 @@ class Instance: # Location of the Instance origin # This origin-position holds regardless of either `reflect` field. # If specified in absolute coordinates location-units are [PrimPitches]. - loc: "Place" + loc: "Place" = field() # Reflect - reflect: Reflect + reflect: Reflect = field(default_factory=Reflect.default) # Boolean indication of whether this Instance is reflected in direction `dir` def reflected(self, dir_: Dir) -> bool: @@ -46,21 +47,28 @@ def __repr__(self): def boundbox(self) -> BoundBox[PrimPitches]: """# Retrieve this Instance's bounding rectangle, specified in [PrimPitches]. # Instance location must be resolved to absolute coordinates, or this method will fail.""" - from .placement import AbsPlace - - if not isinstance(self.loc, AbsPlace): - raise RuntimeError(f"Instance location must be resolved to absolute coordinates") + from .placement import AbsPlace, RelativePlace + + if isinstance(self.loc, RelativePlace): + if self.loc.resolved is None: + msg = f"Instance location must be resolved to absolute coordinates" + raise RuntimeError(msg) + abs_loc = self.loc.resolved + elif isinstance(self.loc, AbsPlace): + abs_loc = self.loc + else: + raise TypeError outline = self.of.outline() if self.reflect.horiz: - (x0, x1) = (self.loc.x - outline.xmax(), self.loc.x) + (x0, x1) = (abs_loc.x - outline.xmax(), abs_loc.x) else: - (x0, x1) = (self.loc.x, self.loc.x + outline.xmax()) + (x0, x1) = (abs_loc.x, abs_loc.x + outline.xmax()) if self.reflect.vert: - (y0, y1) = (self.loc.y - outline.ymax(), self.loc.y) + (y0, y1) = (abs_loc.y - outline.ymax(), abs_loc.y) else: - (y0, y1) = (self.loc.y, self.loc.y + outline.ymax()) + (y0, y1) = (abs_loc.y, abs_loc.y + outline.ymax()) return BoundBox(mins=Xy(x=x0, y=y0), maxs=Xy(x=x1, y=y1)) diff --git a/Tetris/tetris/placement.py b/Tetris/tetris/placement.py index 491f2e0..e7222fb 100644 --- a/Tetris/tetris/placement.py +++ b/Tetris/tetris/placement.py @@ -51,6 +51,10 @@ class AbsPlace: xy: Xy ## FIXME: [PrimPitches] + @staticmethod + def origin() -> "AbsPlace": + return AbsPlace(xy=Xy.origin()) + @property def x(self) -> PrimPitches: return self.xy.x diff --git a/Tetris/tetris/separation.py b/Tetris/tetris/separation.py index 9c6ea67..468d041 100644 --- a/Tetris/tetris/separation.py +++ b/Tetris/tetris/separation.py @@ -17,13 +17,13 @@ class SizeOf: # Enumerated means of specifying x-y relative-placement separation -SepBy = Union[ - UnitSpeced, SizeOf # Absolute valued dimensions # Size of an instantiable object -] +SepBy = Union[UnitSpeced, SizeOf] + -# Three-dimensional separation units @dataclass class Separation: + """# Three-dimensional separation""" + x: Optional[SepBy] = None y: Optional[SepBy] = None z: Optional[int] = None @@ -31,7 +31,7 @@ class Separation: @staticmethod def zero() -> "Separation": return Separation() - + @staticmethod def by_x(x: SepBy) -> "Separation": return Separation(x=x) From 812ee2d7f54cbb27eca7ed45588754534b79e058 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Wed, 25 Jan 2023 13:52:19 -0800 Subject: [PATCH 21/28] Partial #33 Gds to TOML. Upgrade to TOML v0.6. --- Cargo.toml | 4 ++-- gds21/src/tests.rs | 1 - layout21utils/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41a475d..0351596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,13 @@ [workspace] members = [ - "rules21", + # "rules21", "gds21", "layout21", "layout21converters", "layout21protos", "layout21raw", - "layout21tetris", + # "layout21tetris", "layout21utils", "lef21", ] diff --git a/gds21/src/tests.rs b/gds21/src/tests.rs index ea46379..a7a6f2a 100644 --- a/gds21/src/tests.rs +++ b/gds21/src/tests.rs @@ -209,7 +209,6 @@ fn empty_lib_to_yaml() -> GdsResult<()> { Ok(()) } #[test] -#[ignore] // https://github.com/dan-fritchman/Layout21/issues/33 fn empty_lib_to_toml() -> GdsResult<()> { let lib = empty_lib(); Toml.save(&lib, &resource("empty.gds.toml")) diff --git a/layout21utils/Cargo.toml b/layout21utils/Cargo.toml index 4f53d9f..10d9fcf 100644 --- a/layout21utils/Cargo.toml +++ b/layout21utils/Cargo.toml @@ -25,4 +25,4 @@ serde_derive = "1.0.88" serde_json = "1.0" serde_yaml = "0.8" textwrap = "0.14.2" -toml = "0.5.10" +toml = "0.6" From 4e670903d44346713ef3c98ebdc9e466e01dd1f9 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Tue, 31 Jan 2023 09:13:22 -0800 Subject: [PATCH 22/28] Add layout21wgpu crate --- Cargo.toml | 2 + layout21wgpu/Cargo.toml | 40 +++ layout21wgpu/src/lib.rs | 470 +++++++++++++++++++++++++++++++++++ layout21wgpu/src/main.rs | 5 + layout21wgpu/src/shader.wgsl | 22 ++ 5 files changed, 539 insertions(+) create mode 100644 layout21wgpu/Cargo.toml create mode 100644 layout21wgpu/src/lib.rs create mode 100644 layout21wgpu/src/main.rs create mode 100644 layout21wgpu/src/shader.wgsl diff --git a/Cargo.toml b/Cargo.toml index e580050..648525c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,10 @@ members = [ "layout21raw", "layout21tetris", "layout21utils", + "layout21wgpu", "lef21", ] +resolver = "2" # Inherited Package Attributes # Thanks https://doc.rust-lang.org/cargo/reference/workspaces.html#the-package-table! diff --git a/layout21wgpu/Cargo.toml b/layout21wgpu/Cargo.toml new file mode 100644 index 0000000..fb859d6 --- /dev/null +++ b/layout21wgpu/Cargo.toml @@ -0,0 +1,40 @@ +[package] +description = "Layout21 WGPU" +name = "layout21wgpu" + +# Shared layout21 attributes +authors.workspace = true +categories.workspace = true +documentation.workspace = true +edition.workspace = true +exclude.workspace = true +homepage.workspace = true +include.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +workspace = "../" + +[dependencies] +# Local workspace dependencies +layout21protos = {path = "../layout21protos", version = "3.0.0-pre.3"} +layout21raw = {path = "../layout21raw", version = "3.0.0-pre.3"} +layout21utils = {path = "../layout21utils", version = "3.0.0-pre.3"} + +# Crates.io +wgpu = "0.15" +derive_builder = "0.9" +derive_more = "0.99.16" +num-integer = "0.1" +num-traits = "0.2" +serde = {version = "1.0", features = ["derive"]} +serde_derive = "1.0.88" +slotmap = {version = "1.0", features = ["serde"]} +winit = "0.27.5" +rand = "0.8.5" +log = "0.4.17" +bytemuck = { version = "1.4", features = ["derive"] } +env_logger = "0.10.0" +pollster = "0.2.5" diff --git a/layout21wgpu/src/lib.rs b/layout21wgpu/src/lib.rs new file mode 100644 index 0000000..8fbb433 --- /dev/null +++ b/layout21wgpu/src/lib.rs @@ -0,0 +1,470 @@ +use std::{iter, mem}; + +use bytemuck::{Pod, Zeroable}; + +use layout21protos::{self, conv as proto_converters}; +use log::{debug, error, info, log_enabled, Level}; +use rand::Rng; +use wgpu::util::DeviceExt; +use winit::{ + event::*, + event_loop::{ControlFlow, EventLoop}, + window::{Window, WindowBuilder}, +}; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +struct Vertex { + position: [f32; 2], + color: Color, +} +impl Vertex { + const ATTRIBUTES: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x3]; + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBUTES, + } + } +} + +struct Rect { + p0: [f32; 2], + p1: [f32; 2], + color: Color, +} +impl Rect { + /// Triangulated vertices + fn vertices(&self) -> [Vertex; 6] { + let Rect { p0, p1, color } = self; + let [x0, y0] = *p0; + let [x1, y1] = *p1; + [ + Vertex { + position: [x0, y0], + color: *color, + }, + Vertex { + position: [x1, y0], + color: *color, + }, + Vertex { + position: [x0, y1], + color: *color, + }, + Vertex { + position: [x0, y1], + color: *color, + }, + Vertex { + position: [x1, y0], + color: *color, + }, + Vertex { + position: [x1, y1], + color: *color, + }, + ] + } +} + +const NUM_RECTS: usize = 10_000; +const NUM_VERTICES: usize = NUM_RECTS * 6; +struct RectHolder { + rects: Vec, + vertices: Vec, +} +impl RectHolder { + /// Generate a random set of rectangles + fn random() -> RectHolder { + let mut rects = Vec::with_capacity(NUM_RECTS); + let mut rng = rand::thread_rng(); + let mut random = || rng.gen_range(-1.0..1.0); + for _ in 0..NUM_RECTS { + let r = Rect { + p0: [random(), random()], + p1: [random(), random()], + color: Color([random(), random(), random()]), + }; + rects.push(r); + } + let mut vertices = Vec::with_capacity(6 * NUM_RECTS); + for rect in rects.iter() { + vertices.extend_from_slice(&rect.vertices()); + } + Self { rects, vertices } + } +} + +// const NUM_TRIANGLES: usize = 100_000; +// const NUM_VERTICES: usize = NUM_TRIANGLES * 3; + +// fn generate_vertices() -> Vec { +// let mut rng = rand::thread_rng(); +// let mut random = || rng.gen_range(-1.0..1.0); +// let mut vertices = Vec::new(); +// for _ in 0..NUM_VERTICES { +// let x = random(); +// let y = random(); +// let color = Color([random(), random(), random()]); +// vertices.push(Vertex { +// position: [x, y], +// color, +// }); +// } +// vertices +// } + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +struct Color(pub [f32; 3]); + +// const RED: Color = Color([1.0, 0.0, 0.0]); +// const GREEN: Color = Color([0.0, 1.0, 0.0]); +// const BLUE: Color = Color([0.0, 0.0, 1.0]); +// const BLACK: Color = Color([0.0, 0.0, 0.0]); +// const WHITE: Color = Color([1.0, 1.0, 1.0]); + +// const VERTICES: &[Vertex] = &[ +// Vertex { +// // vertex a +// position: [-0.5, -0.5], +// color: RED, +// }, +// Vertex { +// // vertex b +// position: [0.5, -0.5], +// color: RED, +// }, +// Vertex { +// // vertex d +// position: [-0.5, 0.5], +// color: RED, +// }, +// Vertex { +// // vertex d +// position: [-0.5, 0.5], +// color: Color([1.0, 1.0, 0.0]), +// }, +// Vertex { +// // vertex b +// position: [0.5, -0.5], +// color: GREEN, +// }, +// Vertex { +// // vertex c +// position: [0.5, 0.5], +// color: BLUE, +// }, +// Vertex { +// position: [-0.75, 0.25], +// color: Color([1.0, 1.0, 0.0]), +// }, +// Vertex { +// position: [0.75, -0.25], +// color: GREEN, +// }, +// Vertex { +// position: [0.15, 0.95], +// color: BLUE, +// }, +// ]; + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + pipeline: wgpu::RenderPipeline, + vertex_buffer_stuff: VertexBufferStuff, + // vertex_buffer: wgpu::Buffer, + // vertices: Vec, +} +struct OneOfThese { + vertex_buffer: wgpu::Buffer, + vertices: Vec, +} +impl OneOfThese { + fn new(device: &wgpu::Device) -> Self { + // let vertices = generate_vertices(); + let vertices = RectHolder::random().vertices; + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + Self { + vertex_buffer, + vertices, + } + } +} +struct VertexBufferStuff { + one: OneOfThese, + two: OneOfThese, + idx: usize, +} +impl VertexBufferStuff { + fn new(device: &wgpu::Device) -> Self { + let one = OneOfThese::new(device); + let two = OneOfThese::new(device); + Self { one, two, idx: 0 } + } + fn current(&self) -> &OneOfThese { + match self.idx { + 0 => &self.one, + 1 => &self.two, + _ => unreachable!(), + } + } + fn swap(&mut self) { + self.idx = (self.idx + 1) % 2; + } +} + +impl State { + async fn new(window: &Window) -> Self { + let size = window.inner_size(); + let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + dx12_shader_compiler: wgpu::Dx12Compiler::default(), + }); + let surface = unsafe { instance.create_surface(window) }.unwrap(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, // Trace path + ) + .await + .unwrap(); + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: Vec::new(), + }; + surface.configure(&device, &config); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent::OVER, + alpha: wgpu::BlendComponent::OVER, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + // let vertices = generate_vertices(); + // let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + // label: Some("Vertex Buffer"), + // contents: bytemuck::cast_slice(&vertices), + // usage: wgpu::BufferUsages::VERTEX, + // }); + let vertex_buffer_stuff = VertexBufferStuff::new(&device); + + Self { + surface, + device, + queue, + config, + size, + pipeline, + vertex_buffer_stuff, + // vertex_buffer, + // vertices + } + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + } + + #[allow(unused_variables)] + fn input(&mut self, event: &WindowEvent) -> bool { + false + } + + fn update(&mut self) {} + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + //let output = self.surface.get_current_frame()?.output; + // self.vertex_buffer_stuff = VertexBufferStuff::new(&self.device); + // self.vertices = generate_vertices(); + // self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + // label: Some("Vertex Buffer"), + // contents: bytemuck::cast_slice(&self.vertices), + // usage: wgpu::BufferUsages::VERTEX, + // }); + + self.vertex_buffer_stuff.swap(); + + let output = self.surface.get_current_texture()?; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.2, + g: 0.247, + b: 0.314, + a: 1.0, + }), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_vertex_buffer(0, self.vertex_buffer().slice(..)); + render_pass.draw(0..NUM_VERTICES as u32, 0..1); + } + + self.queue.submit(iter::once(encoder.finish())); + output.present(); + + Ok(()) + } + fn vertex_buffer(&self) -> &wgpu::Buffer { + &self.vertex_buffer_stuff.current().vertex_buffer + } +} + +pub fn run() { + env_logger::init(); + let something: layout21protos::Library = + proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + window.set_title(&*format!("{}", "Layout21 Viewer")); + let mut state = pollster::block_on(State::new(&window)); + + error!("START!!!"); + + event_loop.run(move |event, _, control_flow| match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => { + if !state.input(event) { + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(physical_size) => { + state.resize(*physical_size); + window.request_redraw(); + } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + state.resize(**new_inner_size); + } + _ => {} + } + } + } + Event::RedrawRequested(_) => { + error!("REDRAW!!!"); + state.update(); + match state.render() { + Ok(_) => {} + Err(wgpu::SurfaceError::Lost) => state.resize(state.size), + Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, + Err(e) => eprintln!("{:?}", e), + } + } + Event::MainEventsCleared => { + error!("REQUESTING!!!"); + window.request_redraw(); + } + _ => {} + }); +} + +/// Grab the full path of resource-file `fname` +fn resource(rname: &str) -> String { + format!( + "{}/../layout21converters/resources/{}", + env!("CARGO_MANIFEST_DIR"), + rname + ) +} diff --git a/layout21wgpu/src/main.rs b/layout21wgpu/src/main.rs new file mode 100644 index 0000000..b05b64e --- /dev/null +++ b/layout21wgpu/src/main.rs @@ -0,0 +1,5 @@ +use layout21wgpu::run; + +fn main() { + run() +} diff --git a/layout21wgpu/src/shader.wgsl b/layout21wgpu/src/shader.wgsl new file mode 100644 index 0000000..8d3211c --- /dev/null +++ b/layout21wgpu/src/shader.wgsl @@ -0,0 +1,22 @@ +struct VertexInput { + @location(0) pos: vec2, + @location(1) color: vec3, +}; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +}; + +@vertex +fn vs_main(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + out.color = vec4(in.color, 0.5); + out.position = vec4(in.pos, 0.0, 1.0); + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return in.color; +} \ No newline at end of file From e2989e743fd4fe88d6906a16aaeac8f70b0b5efc Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Fri, 3 Feb 2023 17:15:51 -0800 Subject: [PATCH 23/28] WIP layout -> wgpu --- layout21wgpu/src/lib.rs | 71 +++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/layout21wgpu/src/lib.rs b/layout21wgpu/src/lib.rs index 8fbb433..2d96ffa 100644 --- a/layout21wgpu/src/lib.rs +++ b/layout21wgpu/src/lib.rs @@ -1,8 +1,10 @@ -use std::{iter, mem}; +use std::{iter, marker::PhantomData, mem}; use bytemuck::{Pod, Zeroable}; use layout21protos::{self, conv as proto_converters}; +use layout21raw as raw; +use layout21utils::Ptr; use log::{debug, error, info, log_enabled, Level}; use rand::Rng; use wgpu::util::DeviceExt; @@ -12,6 +14,43 @@ use winit::{ window::{Window, WindowBuilder}, }; +#[derive(Debug)] +struct LayoutDisplay { + lib: raw::Library, + cell: Ptr, + bbox: Option<[[raw::Int; 2]; 2]>, +} +impl LayoutDisplay { + fn from_proto() -> Self { + let proto_lib: layout21protos::Library = + proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); + let rawlib = raw::Library::from_proto(proto_lib, None).unwrap(); + Self::from_rawlib(rawlib) + } + fn from_rawlib(rawlib: raw::Library) -> Self { + let cell = rawlib.cells[0].clone(); + + Self { + lib: rawlib, + cell, + bbox: None, + } + } + fn something(&self) { + let bbox = self.bbox.unwrap().clone(); + let cell = self.cell.read().unwrap(); + let layout = cell.layout.as_ref().unwrap(); + for elem in &layout.elems { + let shape = &elem.inner; + match shape { + raw::Shape::Rect(r) => unimplemented!(), + raw::Shape::Path(p) => unimplemented!(), + raw::Shape::Polygon(p) => unimplemented!(), + } + } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] struct Vertex { @@ -172,17 +211,6 @@ struct Color(pub [f32; 3]); // }, // ]; -struct State { - surface: wgpu::Surface, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize, - pipeline: wgpu::RenderPipeline, - vertex_buffer_stuff: VertexBufferStuff, - // vertex_buffer: wgpu::Buffer, - // vertices: Vec, -} struct OneOfThese { vertex_buffer: wgpu::Buffer, vertices: Vec, @@ -225,6 +253,19 @@ impl VertexBufferStuff { } } +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + pipeline: wgpu::RenderPipeline, + vertex_buffer_stuff: VertexBufferStuff, + layout_display: LayoutDisplay, + // vertex_buffer: wgpu::Buffer, + // vertices: Vec, +} + impl State { async fn new(window: &Window) -> Self { let size = window.inner_size(); @@ -317,6 +358,8 @@ impl State { // }); let vertex_buffer_stuff = VertexBufferStuff::new(&device); + let layout_display = LayoutDisplay::from_proto(); + Self { surface, device, @@ -325,6 +368,7 @@ impl State { size, pipeline, vertex_buffer_stuff, + layout_display, // vertex_buffer, // vertices } @@ -405,8 +449,7 @@ impl State { pub fn run() { env_logger::init(); - let something: layout21protos::Library = - proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); + let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); window.set_title(&*format!("{}", "Layout21 Viewer")); From e4cf153a43d3fdafcb1f042d53969544012962e4 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Wed, 8 Feb 2023 14:46:43 -0800 Subject: [PATCH 24/28] Initial WGPU rendering rects & paths --- layout21raw/src/geom.rs | 54 ++++++----- layout21wgpu/src/lib.rs | 176 +++++++++++++++++++++++------------ layout21wgpu/src/shader.wgsl | 2 +- 3 files changed, 147 insertions(+), 85 deletions(-) diff --git a/layout21raw/src/geom.rs b/layout21raw/src/geom.rs index 841e3a2..a0b95ae 100644 --- a/layout21raw/src/geom.rs +++ b/layout21raw/src/geom.rs @@ -100,6 +100,32 @@ pub struct Path { pub points: Vec, pub width: usize, } +impl Path { + /// Convert a Manhattan path into a vector of rectangles. + /// Returns `None` if the path is not Manhattan. + pub fn rects(&self) -> Option> { + let (points, width) = (&self.points, self.width); + let width = Int::try_from(width).unwrap(); // FIXME: probably store these signed, check them on creation + let mut rects: Vec = Vec::with_capacity(self.points.len()); + for k in 0..points.len() - 1 { + let rect = if points[k].x == points[k + 1].x { + Rect { + p0: Point::new(points[k].x - width / 2, points[k].y), + p1: Point::new(points[k].x + width / 2, points[k + 1].y), + } + } else if points[k].y == points[k + 1].y { + Rect { + p0: Point::new(points[k].x, points[k].y - width / 2), + p1: Point::new(points[k + 1].x, points[k].y + width / 2), + } + } else { + return None; // Non-Manhattan Path + }; + rects.push(rect); + } + Some(rects) + } +} /// # Polygon /// /// Closed n-sided polygon with arbitrary number of vertices. @@ -314,33 +340,15 @@ impl ShapeTrait for Path { /// Boolean indication of whether the [Shape] contains [Point] `pt`. /// Containment is *inclusive* for all [Shape] types. /// [Point]s on their boundary, which generally include all points specifying the shape itself, are regarded throughout as "inside" the shape. + /// + /// Note checks for [`Path`] are valid solely for Manhattan paths, i.e. those with segments solely running vertically or horizontally. fn contains(&self, pt: &Point) -> bool { - // Break into segments, and check for intersection with each - // Probably not the most efficient way to do this, but a start. - // Only "Manhattan paths", i.e. those with segments solely running vertically or horizontally, are supported. // FIXME: even with this method, there are some small pieces at corners which we'll miss. // Whether these are relevant in real life, tbd. - let (points, width) = (&self.points, self.width); - let width = Int::try_from(width).unwrap(); // FIXME: probably store these signed, check them on creation - for k in 0..points.len() - 1 { - let rect = if points[k].x == points[k + 1].x { - Rect { - p0: Point::new(points[k].x - width / 2, points[k].y), - p1: Point::new(points[k].x + width / 2, points[k + 1].y), - } - } else if points[k].y == points[k + 1].y { - Rect { - p0: Point::new(points[k].x, points[k].y - width / 2), - p1: Point::new(points[k + 1].x, points[k].y + width / 2), - } - } else { - unimplemented!("Unsupported Non-Manhattan Path") - }; - if rect.contains(pt) { - return true; - } + match self.rects() { + None => false, // FIXME! non-Manhattan paths + Some(rects) => rects.iter().any(|r| r.contains(pt)), } - false } fn to_poly(&self) -> Polygon { unimplemented!("Path::to_poly") diff --git a/layout21wgpu/src/lib.rs b/layout21wgpu/src/lib.rs index 2d96ffa..7f537cd 100644 --- a/layout21wgpu/src/lib.rs +++ b/layout21wgpu/src/lib.rs @@ -1,4 +1,4 @@ -use std::{iter, marker::PhantomData, mem}; +use std::{collections::HashMap, iter, marker::PhantomData, mem}; use bytemuck::{Pod, Zeroable}; @@ -14,40 +14,120 @@ use winit::{ window::{Window, WindowBuilder}, }; +#[derive(Debug, Clone, Copy)] +struct Tbd; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +struct Color(pub [f32; 3]); + +const RED: Color = Color([1.0, 0.0, 0.0]); +const GREEN: Color = Color([0.0, 1.0, 0.0]); +const BLUE: Color = Color([0.0, 0.0, 1.0]); +const BLACK: Color = Color([0.0, 0.0, 0.0]); +const WHITE: Color = Color([1.0, 1.0, 1.0]); + +const colors: [Color; 4] = [RED, GREEN, BLUE, WHITE]; +#[derive(Debug)] +struct ColorWheel { + index: usize, +} +impl ColorWheel { + fn next(&mut self) -> Color { + let color = colors[self.index]; + self.index = (self.index + 1) % colors.len(); + color + } +} + #[derive(Debug)] struct LayoutDisplay { + // Data elements lib: raw::Library, cell: Ptr, - bbox: Option<[[raw::Int; 2]; 2]>, + // Rendering elements + bbox: [[raw::Int; 2]; 2], + vertices: Vec, + layer_colors: HashMap, } impl LayoutDisplay { fn from_proto() -> Self { let proto_lib: layout21protos::Library = proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); let rawlib = raw::Library::from_proto(proto_lib, None).unwrap(); - Self::from_rawlib(rawlib) - } - fn from_rawlib(rawlib: raw::Library) -> Self { let cell = rawlib.cells[0].clone(); - - Self { - lib: rawlib, - cell, - bbox: None, - } + Self::build(rawlib, cell) } - fn something(&self) { - let bbox = self.bbox.unwrap().clone(); - let cell = self.cell.read().unwrap(); - let layout = cell.layout.as_ref().unwrap(); + fn build(rawlib: raw::Library, cell: Ptr) -> Self { + let cell1 = cell.read().unwrap(); + let layout = cell1.layout.as_ref().unwrap(); + + let mut layer_colors: HashMap = HashMap::new(); + + let mut rects: Vec = Vec::with_capacity(layout.elems.len()); + // let bbox = self.bbox.unwrap().clone(); + let bbox = [[0, 10_000], [0, 4000]]; + + let mut color_wheel = ColorWheel { index: 0 }; + for elem in &layout.elems { + let color = layer_colors + .entry(elem.layer) + .or_insert_with(|| color_wheel.next()); let shape = &elem.inner; match shape { - raw::Shape::Rect(r) => unimplemented!(), - raw::Shape::Path(p) => unimplemented!(), - raw::Shape::Polygon(p) => unimplemented!(), + raw::Shape::Rect(r) => { + let rect = Rect { + p0: [ + r.p0.x as f32 / bbox[0][1] as f32, + r.p0.y as f32 / bbox[1][1] as f32, + ], + p1: [ + r.p1.x as f32 / bbox[0][1] as f32, + r.p1.y as f32 / bbox[1][1] as f32, + ], + color: color_wheel.next(), + }; + rects.push(rect); + } + raw::Shape::Path(p) => { + let path_rects = p.rects(); + if path_rects.is_none(){ + continue; + } + let path_rects = path_rects.unwrap(); + for r in path_rects.iter() { + let rect = Rect { + p0: [ + r.p0.x as f32 / bbox[0][1] as f32, + r.p0.y as f32 / bbox[1][1] as f32, + ], + p1: [ + r.p1.x as f32 / bbox[0][1] as f32, + r.p1.y as f32 / bbox[1][1] as f32, + ], + color: color_wheel.next(), + }; + rects.push(rect); + } + } + raw::Shape::Polygon(_) => { + error!("Not implemented: poly") + } } } + + let mut vertices: Vec = Vec::with_capacity(rects.len() * 6); + for rect in rects.iter() { + vertices.extend_from_slice(&rect.vertices()); + } + Self { + lib: rawlib, + cell: cell.clone(), + bbox, + vertices, + layer_colors, + } } } @@ -109,8 +189,7 @@ impl Rect { } } -const NUM_RECTS: usize = 10_000; -const NUM_VERTICES: usize = NUM_RECTS * 6; +// const NUM_VERTICES: usize = NUM_RECTS * 6; struct RectHolder { rects: Vec, vertices: Vec, @@ -118,6 +197,7 @@ struct RectHolder { impl RectHolder { /// Generate a random set of rectangles fn random() -> RectHolder { + let NUM_RECTS: usize = 10_000; let mut rects = Vec::with_capacity(NUM_RECTS); let mut rng = rand::thread_rng(); let mut random = || rng.gen_range(-1.0..1.0); @@ -156,16 +236,6 @@ impl RectHolder { // vertices // } -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct Color(pub [f32; 3]); - -// const RED: Color = Color([1.0, 0.0, 0.0]); -// const GREEN: Color = Color([0.0, 1.0, 0.0]); -// const BLUE: Color = Color([0.0, 0.0, 1.0]); -// const BLACK: Color = Color([0.0, 0.0, 0.0]); -// const WHITE: Color = Color([1.0, 1.0, 1.0]); - // const VERTICES: &[Vertex] = &[ // Vertex { // // vertex a @@ -211,46 +281,27 @@ struct Color(pub [f32; 3]); // }, // ]; -struct OneOfThese { +struct VertexBufferStuff { vertex_buffer: wgpu::Buffer, - vertices: Vec, + layout_display: LayoutDisplay, } -impl OneOfThese { +impl VertexBufferStuff { fn new(device: &wgpu::Device) -> Self { + let layout_display = LayoutDisplay::from_proto(); + // let vertices = generate_vertices(); - let vertices = RectHolder::random().vertices; + // let vertices = RectHolder::random().vertices; + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&vertices), + contents: bytemuck::cast_slice(&layout_display.vertices), usage: wgpu::BufferUsages::VERTEX, }); Self { vertex_buffer, - vertices, - } - } -} -struct VertexBufferStuff { - one: OneOfThese, - two: OneOfThese, - idx: usize, -} -impl VertexBufferStuff { - fn new(device: &wgpu::Device) -> Self { - let one = OneOfThese::new(device); - let two = OneOfThese::new(device); - Self { one, two, idx: 0 } - } - fn current(&self) -> &OneOfThese { - match self.idx { - 0 => &self.one, - 1 => &self.two, - _ => unreachable!(), + layout_display, } } - fn swap(&mut self) { - self.idx = (self.idx + 1) % 2; - } } struct State { @@ -400,7 +451,7 @@ impl State { // usage: wgpu::BufferUsages::VERTEX, // }); - self.vertex_buffer_stuff.swap(); + // self.vertex_buffer_stuff.swap(); let output = self.surface.get_current_texture()?; let view = output @@ -434,7 +485,10 @@ impl State { render_pass.set_pipeline(&self.pipeline); render_pass.set_vertex_buffer(0, self.vertex_buffer().slice(..)); - render_pass.draw(0..NUM_VERTICES as u32, 0..1); + render_pass.draw( + 0..self.vertex_buffer_stuff.layout_display.vertices.len() as u32, + 0..1, + ); } self.queue.submit(iter::once(encoder.finish())); @@ -443,7 +497,7 @@ impl State { Ok(()) } fn vertex_buffer(&self) -> &wgpu::Buffer { - &self.vertex_buffer_stuff.current().vertex_buffer + &self.vertex_buffer_stuff.vertex_buffer } } diff --git a/layout21wgpu/src/shader.wgsl b/layout21wgpu/src/shader.wgsl index 8d3211c..657471b 100644 --- a/layout21wgpu/src/shader.wgsl +++ b/layout21wgpu/src/shader.wgsl @@ -11,7 +11,7 @@ struct VertexOutput { @vertex fn vs_main(in: VertexInput) -> VertexOutput { var out: VertexOutput; - out.color = vec4(in.color, 0.5); + out.color = vec4(in.color, 0.1); out.position = vec4(in.pos, 0.0, 1.0); return out; } From ad293f9d043a021d907624d2363a37e95eb0d3d9 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Wed, 8 Feb 2023 16:05:05 -0800 Subject: [PATCH 25/28] Initial polygon tessellation --- layout21wgpu/Cargo.toml | 1 + layout21wgpu/src/lib.rs | 569 +------------------------------- layout21wgpu/src/run.rs | 606 +++++++++++++++++++++++++++++++++++ layout21wgpu/src/shader.wgsl | 2 +- 4 files changed, 610 insertions(+), 568 deletions(-) create mode 100644 layout21wgpu/src/run.rs diff --git a/layout21wgpu/Cargo.toml b/layout21wgpu/Cargo.toml index fb859d6..1f0d484 100644 --- a/layout21wgpu/Cargo.toml +++ b/layout21wgpu/Cargo.toml @@ -38,3 +38,4 @@ log = "0.4.17" bytemuck = { version = "1.4", features = ["derive"] } env_logger = "0.10.0" pollster = "0.2.5" +lyon = "1.0.1" diff --git a/layout21wgpu/src/lib.rs b/layout21wgpu/src/lib.rs index 7f537cd..39dde27 100644 --- a/layout21wgpu/src/lib.rs +++ b/layout21wgpu/src/lib.rs @@ -1,567 +1,2 @@ -use std::{collections::HashMap, iter, marker::PhantomData, mem}; - -use bytemuck::{Pod, Zeroable}; - -use layout21protos::{self, conv as proto_converters}; -use layout21raw as raw; -use layout21utils::Ptr; -use log::{debug, error, info, log_enabled, Level}; -use rand::Rng; -use wgpu::util::DeviceExt; -use winit::{ - event::*, - event_loop::{ControlFlow, EventLoop}, - window::{Window, WindowBuilder}, -}; - -#[derive(Debug, Clone, Copy)] -struct Tbd; - -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct Color(pub [f32; 3]); - -const RED: Color = Color([1.0, 0.0, 0.0]); -const GREEN: Color = Color([0.0, 1.0, 0.0]); -const BLUE: Color = Color([0.0, 0.0, 1.0]); -const BLACK: Color = Color([0.0, 0.0, 0.0]); -const WHITE: Color = Color([1.0, 1.0, 1.0]); - -const colors: [Color; 4] = [RED, GREEN, BLUE, WHITE]; -#[derive(Debug)] -struct ColorWheel { - index: usize, -} -impl ColorWheel { - fn next(&mut self) -> Color { - let color = colors[self.index]; - self.index = (self.index + 1) % colors.len(); - color - } -} - -#[derive(Debug)] -struct LayoutDisplay { - // Data elements - lib: raw::Library, - cell: Ptr, - // Rendering elements - bbox: [[raw::Int; 2]; 2], - vertices: Vec, - layer_colors: HashMap, -} -impl LayoutDisplay { - fn from_proto() -> Self { - let proto_lib: layout21protos::Library = - proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); - let rawlib = raw::Library::from_proto(proto_lib, None).unwrap(); - let cell = rawlib.cells[0].clone(); - Self::build(rawlib, cell) - } - fn build(rawlib: raw::Library, cell: Ptr) -> Self { - let cell1 = cell.read().unwrap(); - let layout = cell1.layout.as_ref().unwrap(); - - let mut layer_colors: HashMap = HashMap::new(); - - let mut rects: Vec = Vec::with_capacity(layout.elems.len()); - // let bbox = self.bbox.unwrap().clone(); - let bbox = [[0, 10_000], [0, 4000]]; - - let mut color_wheel = ColorWheel { index: 0 }; - - for elem in &layout.elems { - let color = layer_colors - .entry(elem.layer) - .or_insert_with(|| color_wheel.next()); - let shape = &elem.inner; - match shape { - raw::Shape::Rect(r) => { - let rect = Rect { - p0: [ - r.p0.x as f32 / bbox[0][1] as f32, - r.p0.y as f32 / bbox[1][1] as f32, - ], - p1: [ - r.p1.x as f32 / bbox[0][1] as f32, - r.p1.y as f32 / bbox[1][1] as f32, - ], - color: color_wheel.next(), - }; - rects.push(rect); - } - raw::Shape::Path(p) => { - let path_rects = p.rects(); - if path_rects.is_none(){ - continue; - } - let path_rects = path_rects.unwrap(); - for r in path_rects.iter() { - let rect = Rect { - p0: [ - r.p0.x as f32 / bbox[0][1] as f32, - r.p0.y as f32 / bbox[1][1] as f32, - ], - p1: [ - r.p1.x as f32 / bbox[0][1] as f32, - r.p1.y as f32 / bbox[1][1] as f32, - ], - color: color_wheel.next(), - }; - rects.push(rect); - } - } - raw::Shape::Polygon(_) => { - error!("Not implemented: poly") - } - } - } - - let mut vertices: Vec = Vec::with_capacity(rects.len() * 6); - for rect in rects.iter() { - vertices.extend_from_slice(&rect.vertices()); - } - Self { - lib: rawlib, - cell: cell.clone(), - bbox, - vertices, - layer_colors, - } - } -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct Vertex { - position: [f32; 2], - color: Color, -} -impl Vertex { - const ATTRIBUTES: [wgpu::VertexAttribute; 2] = - wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x3]; - fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &Self::ATTRIBUTES, - } - } -} - -struct Rect { - p0: [f32; 2], - p1: [f32; 2], - color: Color, -} -impl Rect { - /// Triangulated vertices - fn vertices(&self) -> [Vertex; 6] { - let Rect { p0, p1, color } = self; - let [x0, y0] = *p0; - let [x1, y1] = *p1; - [ - Vertex { - position: [x0, y0], - color: *color, - }, - Vertex { - position: [x1, y0], - color: *color, - }, - Vertex { - position: [x0, y1], - color: *color, - }, - Vertex { - position: [x0, y1], - color: *color, - }, - Vertex { - position: [x1, y0], - color: *color, - }, - Vertex { - position: [x1, y1], - color: *color, - }, - ] - } -} - -// const NUM_VERTICES: usize = NUM_RECTS * 6; -struct RectHolder { - rects: Vec, - vertices: Vec, -} -impl RectHolder { - /// Generate a random set of rectangles - fn random() -> RectHolder { - let NUM_RECTS: usize = 10_000; - let mut rects = Vec::with_capacity(NUM_RECTS); - let mut rng = rand::thread_rng(); - let mut random = || rng.gen_range(-1.0..1.0); - for _ in 0..NUM_RECTS { - let r = Rect { - p0: [random(), random()], - p1: [random(), random()], - color: Color([random(), random(), random()]), - }; - rects.push(r); - } - let mut vertices = Vec::with_capacity(6 * NUM_RECTS); - for rect in rects.iter() { - vertices.extend_from_slice(&rect.vertices()); - } - Self { rects, vertices } - } -} - -// const NUM_TRIANGLES: usize = 100_000; -// const NUM_VERTICES: usize = NUM_TRIANGLES * 3; - -// fn generate_vertices() -> Vec { -// let mut rng = rand::thread_rng(); -// let mut random = || rng.gen_range(-1.0..1.0); -// let mut vertices = Vec::new(); -// for _ in 0..NUM_VERTICES { -// let x = random(); -// let y = random(); -// let color = Color([random(), random(), random()]); -// vertices.push(Vertex { -// position: [x, y], -// color, -// }); -// } -// vertices -// } - -// const VERTICES: &[Vertex] = &[ -// Vertex { -// // vertex a -// position: [-0.5, -0.5], -// color: RED, -// }, -// Vertex { -// // vertex b -// position: [0.5, -0.5], -// color: RED, -// }, -// Vertex { -// // vertex d -// position: [-0.5, 0.5], -// color: RED, -// }, -// Vertex { -// // vertex d -// position: [-0.5, 0.5], -// color: Color([1.0, 1.0, 0.0]), -// }, -// Vertex { -// // vertex b -// position: [0.5, -0.5], -// color: GREEN, -// }, -// Vertex { -// // vertex c -// position: [0.5, 0.5], -// color: BLUE, -// }, -// Vertex { -// position: [-0.75, 0.25], -// color: Color([1.0, 1.0, 0.0]), -// }, -// Vertex { -// position: [0.75, -0.25], -// color: GREEN, -// }, -// Vertex { -// position: [0.15, 0.95], -// color: BLUE, -// }, -// ]; - -struct VertexBufferStuff { - vertex_buffer: wgpu::Buffer, - layout_display: LayoutDisplay, -} -impl VertexBufferStuff { - fn new(device: &wgpu::Device) -> Self { - let layout_display = LayoutDisplay::from_proto(); - - // let vertices = generate_vertices(); - // let vertices = RectHolder::random().vertices; - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&layout_display.vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - Self { - vertex_buffer, - layout_display, - } - } -} - -struct State { - surface: wgpu::Surface, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize, - pipeline: wgpu::RenderPipeline, - vertex_buffer_stuff: VertexBufferStuff, - layout_display: LayoutDisplay, - // vertex_buffer: wgpu::Buffer, - // vertices: Vec, -} - -impl State { - async fn new(window: &Window) -> Self { - let size = window.inner_size(); - let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - dx12_shader_compiler: wgpu::Dx12Compiler::default(), - }); - let surface = unsafe { instance.create_surface(window) }.unwrap(); - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - }, - None, // Trace path - ) - .await - .unwrap(); - let swapchain_capabilities = surface.get_capabilities(&adapter); - let swapchain_format = swapchain_capabilities.formats[0]; - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: swapchain_format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: Vec::new(), - }; - surface.configure(&device, &config); - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent::OVER, - alpha: wgpu::BlendComponent::OVER, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - }); - - // let vertices = generate_vertices(); - // let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - // label: Some("Vertex Buffer"), - // contents: bytemuck::cast_slice(&vertices), - // usage: wgpu::BufferUsages::VERTEX, - // }); - let vertex_buffer_stuff = VertexBufferStuff::new(&device); - - let layout_display = LayoutDisplay::from_proto(); - - Self { - surface, - device, - queue, - config, - size, - pipeline, - vertex_buffer_stuff, - layout_display, - // vertex_buffer, - // vertices - } - } - - pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } - } - - #[allow(unused_variables)] - fn input(&mut self, event: &WindowEvent) -> bool { - false - } - - fn update(&mut self) {} - - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - //let output = self.surface.get_current_frame()?.output; - // self.vertex_buffer_stuff = VertexBufferStuff::new(&self.device); - // self.vertices = generate_vertices(); - // self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - // label: Some("Vertex Buffer"), - // contents: bytemuck::cast_slice(&self.vertices), - // usage: wgpu::BufferUsages::VERTEX, - // }); - - // self.vertex_buffer_stuff.swap(); - - let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.2, - g: 0.247, - b: 0.314, - a: 1.0, - }), - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_vertex_buffer(0, self.vertex_buffer().slice(..)); - render_pass.draw( - 0..self.vertex_buffer_stuff.layout_display.vertices.len() as u32, - 0..1, - ); - } - - self.queue.submit(iter::once(encoder.finish())); - output.present(); - - Ok(()) - } - fn vertex_buffer(&self) -> &wgpu::Buffer { - &self.vertex_buffer_stuff.vertex_buffer - } -} - -pub fn run() { - env_logger::init(); - - let event_loop = EventLoop::new(); - let window = WindowBuilder::new().build(&event_loop).unwrap(); - window.set_title(&*format!("{}", "Layout21 Viewer")); - let mut state = pollster::block_on(State::new(&window)); - - error!("START!!!"); - - event_loop.run(move |event, _, control_flow| match event { - Event::WindowEvent { - ref event, - window_id, - } if window_id == window.id() => { - if !state.input(event) { - match event { - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, - .. - } => *control_flow = ControlFlow::Exit, - WindowEvent::Resized(physical_size) => { - state.resize(*physical_size); - window.request_redraw(); - } - WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - state.resize(**new_inner_size); - } - _ => {} - } - } - } - Event::RedrawRequested(_) => { - error!("REDRAW!!!"); - state.update(); - match state.render() { - Ok(_) => {} - Err(wgpu::SurfaceError::Lost) => state.resize(state.size), - Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, - Err(e) => eprintln!("{:?}", e), - } - } - Event::MainEventsCleared => { - error!("REQUESTING!!!"); - window.request_redraw(); - } - _ => {} - }); -} - -/// Grab the full path of resource-file `fname` -fn resource(rname: &str) -> String { - format!( - "{}/../layout21converters/resources/{}", - env!("CARGO_MANIFEST_DIR"), - rname - ) -} +mod run; +pub use crate::run::run; diff --git a/layout21wgpu/src/run.rs b/layout21wgpu/src/run.rs new file mode 100644 index 0000000..728f017 --- /dev/null +++ b/layout21wgpu/src/run.rs @@ -0,0 +1,606 @@ +use bytemuck::{Pod, Zeroable}; +use lyon::{ + geom::Box2D, + math::point, + path::polygon::Polygon, + tessellation::{ + geometry_builder::{BuffersBuilder, VertexBuffers}, + FillOptions, FillTessellator, FillVertex, FillVertexConstructor, + }, +}; +use std::{collections::HashMap, iter, marker::PhantomData, mem}; + +use layout21protos::{self, conv as proto_converters}; +use layout21raw as raw; +use layout21utils::Ptr; +use log::{debug, error, info, log_enabled, Level}; +use rand::Rng; +use wgpu::util::DeviceExt; +use winit::{ + event::*, + event_loop::{ControlFlow, EventLoop}, + window::{Window, WindowBuilder}, +}; + +#[derive(Debug, Clone, Copy)] +struct Tbd; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +struct Color(pub [f32; 3]); + +const COLORS: [Color; 7] = [ + Color([1.0, 0.0, 0.0]), // red + Color([0.0, 1.0, 0.0]), // green + Color([0.0, 0.0, 1.0]), // blue + Color([1.0, 1.0, 0.0]), // + Color([1.0, 0.0, 1.0]), // + Color([0.0, 1.0, 1.0]), // + Color([1.0, 1.0, 1.0]), // white +]; +#[derive(Debug)] +struct ColorWheel { + index: usize, +} +impl ColorWheel { + fn next(&mut self) -> Color { + let color = COLORS[self.index]; + self.index = (self.index + 1) % COLORS.len(); + color + } +} + +#[derive(Debug)] +struct LayoutDisplay { + // Data elements + lib: raw::Library, + cell: Ptr, + // Rendering elements + bbox: [[raw::Int; 2]; 2], + vertices: Vec, + layer_colors: HashMap, + geometry: VertexBuffers, +} +impl LayoutDisplay { + fn from_proto() -> Self { + let proto_lib: layout21protos::Library = + proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); + let rawlib = raw::Library::from_proto(proto_lib, None).unwrap(); + let cell = rawlib.cells[0].clone(); + Self::build(rawlib, cell) + } + fn build(rawlib: raw::Library, cell: Ptr) -> Self { + let cell1 = cell.read().unwrap(); + let layout = cell1.layout.as_ref().unwrap(); + + let mut layer_colors: HashMap = HashMap::new(); + + let mut rects: Vec = Vec::with_capacity(layout.elems.len()); + let bbox = [[0, 10_000], [0, 4000]]; // FIXME! + + let mut color_wheel = ColorWheel { index: 0 }; + let mut tessellator = FillTessellator::new(); + let mut geometry: VertexBuffers = VertexBuffers::new(); + + let mut k = 0; + for elem in &layout.elems { + let color = layer_colors + .entry(elem.layer) + .or_insert_with(|| color_wheel.next()) + .clone(); + + let shape = &elem.inner; + match shape { + raw::Shape::Rect(r) => { + // let rect = Rect { + // p0: [ + // r.p0.x as f32 / bbox[0][1] as f32, + // r.p0.y as f32 / bbox[1][1] as f32, + // ], + // p1: [ + // r.p1.x as f32 / bbox[0][1] as f32, + // r.p1.y as f32 / bbox[1][1] as f32, + // ], + // color, + // }; + // rects.push(rect); + + let lyon_rect = Box2D::new( + point( + r.p0.x as f32 / bbox[0][1] as f32, + r.p0.y as f32 / bbox[1][1] as f32, + ), + point( + r.p1.x as f32 / bbox[0][1] as f32, + r.p1.y as f32 / bbox[1][1] as f32, + ), + ); + tessellator + .tessellate_rectangle( + &lyon_rect, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), + ) + .unwrap(); + } + raw::Shape::Path(p) => { + let path_rects = p.rects(); + if path_rects.is_none() { + continue; + } + let path_rects = path_rects.unwrap(); + for r in path_rects.iter() { + // let rect = Rect { + // p0: [ + // r.p0.x as f32 / bbox[0][1] as f32, + // r.p0.y as f32 / bbox[1][1] as f32, + // ], + // p1: [ + // r.p1.x as f32 / bbox[0][1] as f32, + // r.p1.y as f32 / bbox[1][1] as f32, + // ], + // color, + // }; + // rects.push(rect); + + let lyon_rect = Box2D::new( + point( + r.p0.x as f32 / bbox[0][1] as f32, + r.p0.y as f32 / bbox[1][1] as f32, + ), + point( + r.p1.x as f32 / bbox[0][1] as f32, + r.p1.y as f32 / bbox[1][1] as f32, + ), + ); + tessellator + .tessellate_rectangle( + &lyon_rect, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), + ) + .unwrap(); + } + } + raw::Shape::Polygon(p) => { + let points: Vec<_> = p + .points + .iter() + .map(|p| { + lyon::math::Point::new( + p.x as f32 / bbox[0][1] as f32, + p.y as f32 / bbox[1][1] as f32, + ) + }) + .collect(); + let lyon_polygon: Polygon<_> = Polygon { + points: &points, + closed: true, + }; + tessellator + .tessellate_polygon( + lyon_polygon, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), + ) + .unwrap(); + } + } + if k > 250 { + break; + } + k += 1; + } + + let mut vertices: Vec = Vec::with_capacity(rects.len() * 6); + for rect in rects.iter() { + vertices.extend_from_slice(&rect.vertices()); + } + Self { + lib: rawlib, + cell: cell.clone(), + bbox, + vertices, + layer_colors, + geometry, + } + } +} + +pub struct WithColor { + color: Color, +} +impl WithColor { + fn new(color: Color) -> Self { + Self { color } + } +} +impl FillVertexConstructor for WithColor { + fn new_vertex(&mut self, vertex: FillVertex) -> Vertex { + Vertex { + position: vertex.position().to_array(), + color: self.color.clone(), + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +struct Vertex { + position: [f32; 2], + color: Color, +} +impl Vertex { + const ATTRIBUTES: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x3]; + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBUTES, + } + } +} + +struct Rect { + p0: [f32; 2], + p1: [f32; 2], + color: Color, +} +impl Rect { + /// Triangulated vertices + fn vertices(&self) -> [Vertex; 6] { + let Rect { p0, p1, color } = self; + let [x0, y0] = *p0; + let [x1, y1] = *p1; + [ + Vertex { + position: [x0, y0], + color: *color, + }, + Vertex { + position: [x1, y0], + color: *color, + }, + Vertex { + position: [x0, y1], + color: *color, + }, + Vertex { + position: [x0, y1], + color: *color, + }, + Vertex { + position: [x1, y0], + color: *color, + }, + Vertex { + position: [x1, y1], + color: *color, + }, + ] + } +} + +// const NUM_VERTICES: usize = NUM_RECTS * 6; +struct RectHolder { + rects: Vec, + vertices: Vec, +} +impl RectHolder { + /// Generate a random set of rectangles + fn random() -> RectHolder { + let NUM_RECTS: usize = 10_000; + let mut rects = Vec::with_capacity(NUM_RECTS); + let mut rng = rand::thread_rng(); + let mut random = || rng.gen_range(-1.0..1.0); + for _ in 0..NUM_RECTS { + let r = Rect { + p0: [random(), random()], + p1: [random(), random()], + color: Color([random(), random(), random()]), + }; + rects.push(r); + } + let mut vertices = Vec::with_capacity(6 * NUM_RECTS); + for rect in rects.iter() { + vertices.extend_from_slice(&rect.vertices()); + } + Self { rects, vertices } + } +} + +struct VertexBufferStuff { + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + layout_display: LayoutDisplay, +} +impl VertexBufferStuff { + fn new(device: &wgpu::Device) -> Self { + let layout_display = LayoutDisplay::from_proto(); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&layout_display.geometry.vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&layout_display.geometry.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + Self { + index_buffer, + vertex_buffer, + layout_display, + } + } +} + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + pipeline: wgpu::RenderPipeline, + vertex_buffer_stuff: VertexBufferStuff, + layout_display: LayoutDisplay, +} + +impl State { + async fn new(window: &Window) -> Self { + let size = window.inner_size(); + let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + dx12_shader_compiler: wgpu::Dx12Compiler::default(), + }); + let surface = unsafe { instance.create_surface(window) }.unwrap(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, // Trace path + ) + .await + .unwrap(); + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: Vec::new(), + }; + surface.configure(&device, &config); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + // let vertices = generate_vertices(); + // let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + // label: Some("Vertex Buffer"), + // contents: bytemuck::cast_slice(&vertices), + // usage: wgpu::BufferUsages::VERTEX, + // }); + let vertex_buffer_stuff = VertexBufferStuff::new(&device); + + let layout_display = LayoutDisplay::from_proto(); + + Self { + surface, + device, + queue, + config, + size, + pipeline, + vertex_buffer_stuff, + layout_display, + // vertex_buffer, + // vertices + } + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + } + + #[allow(unused_variables)] + fn input(&mut self, event: &WindowEvent) -> bool { + false + } + + fn update(&mut self) {} + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + //let output = self.surface.get_current_frame()?.output; + // self.vertex_buffer_stuff = VertexBufferStuff::new(&self.device); + // self.vertices = generate_vertices(); + // self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + // label: Some("Vertex Buffer"), + // contents: bytemuck::cast_slice(&self.vertices), + // usage: wgpu::BufferUsages::VERTEX, + // }); + + // self.vertex_buffer_stuff.swap(); + + let output = self.surface.get_current_texture()?; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.2, + g: 0.247, + b: 0.314, + a: 1.0, + }), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_vertex_buffer(0, self.vertex_buffer().slice(..)); + render_pass.set_index_buffer( + self.vertex_buffer_stuff.index_buffer.slice(..), + wgpu::IndexFormat::Uint16, + ); + render_pass.draw_indexed( + 0..self // indices + .vertex_buffer_stuff + .layout_display + .geometry + .indices + .len() as u32, + 0, // base_vertex + 0..1, // instances + ); + } + + self.queue.submit(iter::once(encoder.finish())); + output.present(); + + Ok(()) + } + fn vertex_buffer(&self) -> &wgpu::Buffer { + &self.vertex_buffer_stuff.vertex_buffer + } +} + +pub fn run() { + env_logger::init(); + + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + window.set_title(&*format!("{}", "Layout21 Viewer")); + let mut state = pollster::block_on(State::new(&window)); + + error!("START!!!"); + + event_loop.run(move |event, _, control_flow| match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => { + if !state.input(event) { + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(physical_size) => { + state.resize(*physical_size); + window.request_redraw(); + } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + state.resize(**new_inner_size); + } + _ => {} + } + } + } + Event::RedrawRequested(_) => { + error!("REDRAW!!!"); + state.update(); + match state.render() { + Ok(_) => {} + Err(wgpu::SurfaceError::Lost) => state.resize(state.size), + Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, + Err(e) => eprintln!("{:?}", e), + } + } + Event::MainEventsCleared => { + // error!("REQUESTING!!!"); + // window.request_redraw(); + } + _ => {} + }); +} + +/// Grab the full path of resource-file `fname` +fn resource(rname: &str) -> String { + format!( + "{}/../layout21converters/resources/{}", + env!("CARGO_MANIFEST_DIR"), + rname + ) +} diff --git a/layout21wgpu/src/shader.wgsl b/layout21wgpu/src/shader.wgsl index 657471b..5da115f 100644 --- a/layout21wgpu/src/shader.wgsl +++ b/layout21wgpu/src/shader.wgsl @@ -11,7 +11,7 @@ struct VertexOutput { @vertex fn vs_main(in: VertexInput) -> VertexOutput { var out: VertexOutput; - out.color = vec4(in.color, 0.1); + out.color = vec4(in.color, 0.3); out.position = vec4(in.pos, 0.0, 1.0); return out; } From e0914f29a8fa1bbc1a24392b595ea99eeadd7446 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Wed, 8 Feb 2023 21:02:46 -0800 Subject: [PATCH 26/28] Module-ize --- layout21wgpu/src/color.rs | 29 ++ layout21wgpu/src/gpu.rs | 194 +++++++++++++ layout21wgpu/src/layout.rs | 166 +++++++++++ layout21wgpu/src/lib.rs | 13 + layout21wgpu/src/run.rs | 562 ++----------------------------------- layout21wgpu/src/vertex.rs | 23 ++ 6 files changed, 444 insertions(+), 543 deletions(-) create mode 100644 layout21wgpu/src/color.rs create mode 100644 layout21wgpu/src/gpu.rs create mode 100644 layout21wgpu/src/layout.rs create mode 100644 layout21wgpu/src/vertex.rs diff --git a/layout21wgpu/src/color.rs b/layout21wgpu/src/color.rs new file mode 100644 index 0000000..c358330 --- /dev/null +++ b/layout21wgpu/src/color.rs @@ -0,0 +1,29 @@ +use bytemuck::{Pod, Zeroable}; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct Color(pub [f32; 3]); + +pub const COLORS: [Color; 7] = [ + Color([1.0, 0.0, 0.0]), // red + Color([0.0, 1.0, 0.0]), // green + Color([0.0, 0.0, 1.0]), // blue + Color([1.0, 1.0, 0.0]), // + Color([1.0, 0.0, 1.0]), // + Color([0.0, 1.0, 1.0]), // + Color([1.0, 1.0, 1.0]), // white +]; +#[derive(Debug)] +pub(crate) struct ColorWheel { + index: usize, +} +impl ColorWheel { + pub fn new() -> Self { + Self { index: 0 } + } + pub fn next(&mut self) -> Color { + let color = COLORS[self.index]; + self.index = (self.index + 1) % COLORS.len(); + color + } +} diff --git a/layout21wgpu/src/gpu.rs b/layout21wgpu/src/gpu.rs new file mode 100644 index 0000000..e2ba7c4 --- /dev/null +++ b/layout21wgpu/src/gpu.rs @@ -0,0 +1,194 @@ +//! +//! # GPU Stuff +//! +//! All the WGPU machinery lives here. +//! Many, many terms of art fly around GPU world; get used to it. +//! + +use wgpu::util::DeviceExt; +use winit::window::Window; + +// Local Imports +use crate::{LayoutDisplay, Vertex}; + +/// # GPU Stuff +/// +/// *Microooo-processors*. Idunno what they are, you dunno what they are, CASH. +/// +pub struct GpuStuff { + pub surface: wgpu::Surface, + pub device: wgpu::Device, + pub queue: wgpu::Queue, + pub config: wgpu::SurfaceConfiguration, + pub size: winit::dpi::PhysicalSize, + pub pipeline: wgpu::RenderPipeline, + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, +} +impl GpuStuff { + /// Create new [`GpuStuff`]. + /// + /// Much of the machinery and terminology lives here in the configuration phase. + /// Once this gets done, things really do calm down to just the vertex and index buffers, + /// and writing triangles to them. + /// + pub async fn new(window: &Window, layout: &LayoutDisplay) -> Self { + let size = window.inner_size(); + let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + dx12_shader_compiler: wgpu::Dx12Compiler::default(), + }); + let surface = unsafe { instance.create_surface(window) }.unwrap(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, // Trace path + ) + .await + .unwrap(); + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: Vec::new(), + }; + surface.configure(&device, &config); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&layout.geometry.vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&layout.geometry.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + Self { + surface, + device, + queue, + config, + size, + pipeline, + vertex_buffer, + index_buffer, + } + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + } + + /// Render the current frame + pub fn render(&self, max_index: u32) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture()?; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.2, + g: 0.247, + b: 0.314, + a: 1.0, + }), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed( + 0..max_index, // indices + 0, // base_vertex + 0..1, // instances + ); + } + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} diff --git a/layout21wgpu/src/layout.rs b/layout21wgpu/src/layout.rs new file mode 100644 index 0000000..6f7fc2f --- /dev/null +++ b/layout21wgpu/src/layout.rs @@ -0,0 +1,166 @@ +use lyon::{ + geom::Box2D, + math::point, + path::polygon::Polygon, + tessellation::{ + geometry_builder::{BuffersBuilder, VertexBuffers}, + FillOptions, FillTessellator, FillVertex, FillVertexConstructor, + }, +}; +use std::collections::HashMap; + +use layout21protos::{self, conv as proto_converters}; +use layout21raw as raw; +use layout21utils::Ptr; +use log::error; +// Local imports +use crate::{Color, ColorWheel, Vertex}; + +#[derive(Debug)] +pub struct LayoutDisplay { + // Data elements + pub lib: raw::Library, + pub cell: Ptr, + + // Rendering elements + pub bbox: raw::BoundBox, + pub layer_colors: HashMap, + pub geometry: VertexBuffers, +} +impl LayoutDisplay { + pub fn from_proto() -> Self { + let proto_lib: layout21protos::Library = + proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); + let rawlib = raw::Library::from_proto(proto_lib, None).unwrap(); + let cell = rawlib.cells[0].clone(); + Self::build(rawlib, cell) + } + pub fn build(rawlib: raw::Library, cell: Ptr) -> Self { + let cell1 = cell.read().unwrap(); + let layout = cell1.layout.as_ref().unwrap(); + + let mut layer_colors: HashMap = HashMap::new(); + + // let mut rects: Vec = Vec::with_capacity(layout.elems.len()); + let bbox: raw::BoundBox = layout.bbox(); + error!("bbox: {:?}", bbox); + + let mut color_wheel = ColorWheel::new(); + let mut tessellator = FillTessellator::new(); + let mut geometry: VertexBuffers = VertexBuffers::new(); + + for elem in &layout.elems { + let color = layer_colors + .entry(elem.layer) + .or_insert_with(|| color_wheel.next()) + .clone(); + + let shape = &elem.inner; + match shape { + raw::Shape::Rect(r) => { + let lyon_rect = Box2D::new( + point( + r.p0.x as f32 / bbox.p1.x as f32, + r.p0.y as f32 / bbox.p1.y as f32, + ), + point( + r.p1.x as f32 / bbox.p1.x as f32, + r.p1.y as f32 / bbox.p1.y as f32, + ), + ); + tessellator + .tessellate_rectangle( + &lyon_rect, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), + ) + .unwrap(); + } + raw::Shape::Path(p) => { + let path_rects = p.rects(); + if path_rects.is_none() { + continue; + } + let path_rects = path_rects.unwrap(); + for r in path_rects.iter() { + let lyon_rect = Box2D::new( + point( + r.p0.x as f32 / bbox.p1.x as f32, + r.p0.y as f32 / bbox.p1.y as f32, + ), + point( + r.p1.x as f32 / bbox.p1.x as f32, + r.p1.y as f32 / bbox.p1.y as f32, + ), + ); + tessellator + .tessellate_rectangle( + &lyon_rect, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), + ) + .unwrap(); + } + } + raw::Shape::Polygon(p) => { + let points: Vec<_> = p + .points + .iter() + .map(|p| { + lyon::math::Point::new( + p.x as f32 / bbox.p1.x as f32, + p.y as f32 / bbox.p1.y as f32, + ) + }) + .collect(); + let lyon_polygon: Polygon<_> = Polygon { + points: &points, + closed: true, + }; + tessellator + .tessellate_polygon( + lyon_polygon, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), + ) + .unwrap(); + } + } + } + + Self { + lib: rawlib, + cell: cell.clone(), + bbox, + + layer_colors, + geometry, + } + } +} + +pub struct WithColor { + pub color: Color, +} +impl WithColor { + fn new(color: Color) -> Self { + Self { color } + } +} +impl FillVertexConstructor for WithColor { + fn new_vertex(&mut self, vertex: FillVertex) -> Vertex { + Vertex { + position: vertex.position().to_array(), + color: self.color.clone(), + } + } +} + +/// Grab the full path of resource-file `fname` +fn resource(rname: &str) -> String { + format!( + "{}/../layout21converters/resources/{}", + env!("CARGO_MANIFEST_DIR"), + rname + ) +} diff --git a/layout21wgpu/src/lib.rs b/layout21wgpu/src/lib.rs index 39dde27..4522549 100644 --- a/layout21wgpu/src/lib.rs +++ b/layout21wgpu/src/lib.rs @@ -1,2 +1,15 @@ +mod color; +use crate::color::{Color, ColorWheel}; + +mod gpu; +use crate::gpu::GpuStuff; + +mod vertex; +use crate::vertex::Vertex; + +mod layout; +use crate::layout::LayoutDisplay; + +// Primary public export: the run function mod run; pub use crate::run::run; diff --git a/layout21wgpu/src/run.rs b/layout21wgpu/src/run.rs index 728f017..8fe90c3 100644 --- a/layout21wgpu/src/run.rs +++ b/layout21wgpu/src/run.rs @@ -1,543 +1,28 @@ -use bytemuck::{Pod, Zeroable}; -use lyon::{ - geom::Box2D, - math::point, - path::polygon::Polygon, - tessellation::{ - geometry_builder::{BuffersBuilder, VertexBuffers}, - FillOptions, FillTessellator, FillVertex, FillVertexConstructor, - }, -}; -use std::{collections::HashMap, iter, marker::PhantomData, mem}; - -use layout21protos::{self, conv as proto_converters}; -use layout21raw as raw; -use layout21utils::Ptr; -use log::{debug, error, info, log_enabled, Level}; -use rand::Rng; -use wgpu::util::DeviceExt; +use log::error; use winit::{ event::*, event_loop::{ControlFlow, EventLoop}, window::{Window, WindowBuilder}, }; -#[derive(Debug, Clone, Copy)] -struct Tbd; - -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct Color(pub [f32; 3]); - -const COLORS: [Color; 7] = [ - Color([1.0, 0.0, 0.0]), // red - Color([0.0, 1.0, 0.0]), // green - Color([0.0, 0.0, 1.0]), // blue - Color([1.0, 1.0, 0.0]), // - Color([1.0, 0.0, 1.0]), // - Color([0.0, 1.0, 1.0]), // - Color([1.0, 1.0, 1.0]), // white -]; -#[derive(Debug)] -struct ColorWheel { - index: usize, -} -impl ColorWheel { - fn next(&mut self) -> Color { - let color = COLORS[self.index]; - self.index = (self.index + 1) % COLORS.len(); - color - } -} - -#[derive(Debug)] -struct LayoutDisplay { - // Data elements - lib: raw::Library, - cell: Ptr, - // Rendering elements - bbox: [[raw::Int; 2]; 2], - vertices: Vec, - layer_colors: HashMap, - geometry: VertexBuffers, -} -impl LayoutDisplay { - fn from_proto() -> Self { - let proto_lib: layout21protos::Library = - proto_converters::open(&resource("sky130_fd_sc_hd__dfxtp_1.pb")).unwrap(); - let rawlib = raw::Library::from_proto(proto_lib, None).unwrap(); - let cell = rawlib.cells[0].clone(); - Self::build(rawlib, cell) - } - fn build(rawlib: raw::Library, cell: Ptr) -> Self { - let cell1 = cell.read().unwrap(); - let layout = cell1.layout.as_ref().unwrap(); - - let mut layer_colors: HashMap = HashMap::new(); - - let mut rects: Vec = Vec::with_capacity(layout.elems.len()); - let bbox = [[0, 10_000], [0, 4000]]; // FIXME! - - let mut color_wheel = ColorWheel { index: 0 }; - let mut tessellator = FillTessellator::new(); - let mut geometry: VertexBuffers = VertexBuffers::new(); - - let mut k = 0; - for elem in &layout.elems { - let color = layer_colors - .entry(elem.layer) - .or_insert_with(|| color_wheel.next()) - .clone(); - - let shape = &elem.inner; - match shape { - raw::Shape::Rect(r) => { - // let rect = Rect { - // p0: [ - // r.p0.x as f32 / bbox[0][1] as f32, - // r.p0.y as f32 / bbox[1][1] as f32, - // ], - // p1: [ - // r.p1.x as f32 / bbox[0][1] as f32, - // r.p1.y as f32 / bbox[1][1] as f32, - // ], - // color, - // }; - // rects.push(rect); - - let lyon_rect = Box2D::new( - point( - r.p0.x as f32 / bbox[0][1] as f32, - r.p0.y as f32 / bbox[1][1] as f32, - ), - point( - r.p1.x as f32 / bbox[0][1] as f32, - r.p1.y as f32 / bbox[1][1] as f32, - ), - ); - tessellator - .tessellate_rectangle( - &lyon_rect, - &FillOptions::DEFAULT, - &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), - ) - .unwrap(); - } - raw::Shape::Path(p) => { - let path_rects = p.rects(); - if path_rects.is_none() { - continue; - } - let path_rects = path_rects.unwrap(); - for r in path_rects.iter() { - // let rect = Rect { - // p0: [ - // r.p0.x as f32 / bbox[0][1] as f32, - // r.p0.y as f32 / bbox[1][1] as f32, - // ], - // p1: [ - // r.p1.x as f32 / bbox[0][1] as f32, - // r.p1.y as f32 / bbox[1][1] as f32, - // ], - // color, - // }; - // rects.push(rect); - - let lyon_rect = Box2D::new( - point( - r.p0.x as f32 / bbox[0][1] as f32, - r.p0.y as f32 / bbox[1][1] as f32, - ), - point( - r.p1.x as f32 / bbox[0][1] as f32, - r.p1.y as f32 / bbox[1][1] as f32, - ), - ); - tessellator - .tessellate_rectangle( - &lyon_rect, - &FillOptions::DEFAULT, - &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), - ) - .unwrap(); - } - } - raw::Shape::Polygon(p) => { - let points: Vec<_> = p - .points - .iter() - .map(|p| { - lyon::math::Point::new( - p.x as f32 / bbox[0][1] as f32, - p.y as f32 / bbox[1][1] as f32, - ) - }) - .collect(); - let lyon_polygon: Polygon<_> = Polygon { - points: &points, - closed: true, - }; - tessellator - .tessellate_polygon( - lyon_polygon, - &FillOptions::DEFAULT, - &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), - ) - .unwrap(); - } - } - if k > 250 { - break; - } - k += 1; - } - - let mut vertices: Vec = Vec::with_capacity(rects.len() * 6); - for rect in rects.iter() { - vertices.extend_from_slice(&rect.vertices()); - } - Self { - lib: rawlib, - cell: cell.clone(), - bbox, - vertices, - layer_colors, - geometry, - } - } -} - -pub struct WithColor { - color: Color, -} -impl WithColor { - fn new(color: Color) -> Self { - Self { color } - } -} -impl FillVertexConstructor for WithColor { - fn new_vertex(&mut self, vertex: FillVertex) -> Vertex { - Vertex { - position: vertex.position().to_array(), - color: self.color.clone(), - } - } -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct Vertex { - position: [f32; 2], - color: Color, -} -impl Vertex { - const ATTRIBUTES: [wgpu::VertexAttribute; 2] = - wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x3]; - fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &Self::ATTRIBUTES, - } - } -} - -struct Rect { - p0: [f32; 2], - p1: [f32; 2], - color: Color, -} -impl Rect { - /// Triangulated vertices - fn vertices(&self) -> [Vertex; 6] { - let Rect { p0, p1, color } = self; - let [x0, y0] = *p0; - let [x1, y1] = *p1; - [ - Vertex { - position: [x0, y0], - color: *color, - }, - Vertex { - position: [x1, y0], - color: *color, - }, - Vertex { - position: [x0, y1], - color: *color, - }, - Vertex { - position: [x0, y1], - color: *color, - }, - Vertex { - position: [x1, y0], - color: *color, - }, - Vertex { - position: [x1, y1], - color: *color, - }, - ] - } -} - -// const NUM_VERTICES: usize = NUM_RECTS * 6; -struct RectHolder { - rects: Vec, - vertices: Vec, -} -impl RectHolder { - /// Generate a random set of rectangles - fn random() -> RectHolder { - let NUM_RECTS: usize = 10_000; - let mut rects = Vec::with_capacity(NUM_RECTS); - let mut rng = rand::thread_rng(); - let mut random = || rng.gen_range(-1.0..1.0); - for _ in 0..NUM_RECTS { - let r = Rect { - p0: [random(), random()], - p1: [random(), random()], - color: Color([random(), random(), random()]), - }; - rects.push(r); - } - let mut vertices = Vec::with_capacity(6 * NUM_RECTS); - for rect in rects.iter() { - vertices.extend_from_slice(&rect.vertices()); - } - Self { rects, vertices } - } -} - -struct VertexBufferStuff { - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - layout_display: LayoutDisplay, -} -impl VertexBufferStuff { - fn new(device: &wgpu::Device) -> Self { - let layout_display = LayoutDisplay::from_proto(); - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&layout_display.geometry.vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(&layout_display.geometry.indices), - usage: wgpu::BufferUsages::INDEX, - }); +// Local imports +use crate::{GpuStuff, LayoutDisplay}; - Self { - index_buffer, - vertex_buffer, - layout_display, - } - } -} - -struct State { - surface: wgpu::Surface, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize, - pipeline: wgpu::RenderPipeline, - vertex_buffer_stuff: VertexBufferStuff, - layout_display: LayoutDisplay, +pub struct State { + gpu: GpuStuff, + layout: LayoutDisplay, } - impl State { async fn new(window: &Window) -> Self { - let size = window.inner_size(); - let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - dx12_shader_compiler: wgpu::Dx12Compiler::default(), - }); - let surface = unsafe { instance.create_surface(window) }.unwrap(); - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - }, - None, // Trace path - ) - .await - .unwrap(); - let swapchain_capabilities = surface.get_capabilities(&adapter); - let swapchain_format = swapchain_capabilities.formats[0]; - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: swapchain_format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: Vec::new(), - }; - surface.configure(&device, &config); - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - }); - - // let vertices = generate_vertices(); - // let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - // label: Some("Vertex Buffer"), - // contents: bytemuck::cast_slice(&vertices), - // usage: wgpu::BufferUsages::VERTEX, - // }); - let vertex_buffer_stuff = VertexBufferStuff::new(&device); - - let layout_display = LayoutDisplay::from_proto(); - - Self { - surface, - device, - queue, - config, - size, - pipeline, - vertex_buffer_stuff, - layout_display, - // vertex_buffer, - // vertices - } - } - - pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } + let layout = LayoutDisplay::from_proto(); + let gpu = GpuStuff::new(window, &layout).await; + Self { gpu, layout } } +} - #[allow(unused_variables)] - fn input(&mut self, event: &WindowEvent) -> bool { - false - } - - fn update(&mut self) {} - - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - //let output = self.surface.get_current_frame()?.output; - // self.vertex_buffer_stuff = VertexBufferStuff::new(&self.device); - // self.vertices = generate_vertices(); - // self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - // label: Some("Vertex Buffer"), - // contents: bytemuck::cast_slice(&self.vertices), - // usage: wgpu::BufferUsages::VERTEX, - // }); - - // self.vertex_buffer_stuff.swap(); - - let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.2, - g: 0.247, - b: 0.314, - a: 1.0, - }), - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_vertex_buffer(0, self.vertex_buffer().slice(..)); - render_pass.set_index_buffer( - self.vertex_buffer_stuff.index_buffer.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.draw_indexed( - 0..self // indices - .vertex_buffer_stuff - .layout_display - .geometry - .indices - .len() as u32, - 0, // base_vertex - 0..1, // instances - ); - } - - self.queue.submit(iter::once(encoder.finish())); - output.present(); - - Ok(()) - } - fn vertex_buffer(&self) -> &wgpu::Buffer { - &self.vertex_buffer_stuff.vertex_buffer - } +#[allow(unused_variables)] +fn handle_input(event: &WindowEvent) -> bool { + false } pub fn run() { @@ -555,7 +40,7 @@ pub fn run() { ref event, window_id, } if window_id == window.id() => { - if !state.input(event) { + if !handle_input(event) { match event { WindowEvent::CloseRequested | WindowEvent::KeyboardInput { @@ -568,11 +53,11 @@ pub fn run() { .. } => *control_flow = ControlFlow::Exit, WindowEvent::Resized(physical_size) => { - state.resize(*physical_size); + state.gpu.resize(*physical_size); window.request_redraw(); } WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - state.resize(**new_inner_size); + state.gpu.resize(**new_inner_size); } _ => {} } @@ -580,10 +65,10 @@ pub fn run() { } Event::RedrawRequested(_) => { error!("REDRAW!!!"); - state.update(); - match state.render() { + let max_index = state.layout.geometry.indices.len(); + match state.gpu.render(max_index as u32) { Ok(_) => {} - Err(wgpu::SurfaceError::Lost) => state.resize(state.size), + Err(wgpu::SurfaceError::Lost) => state.gpu.resize(state.gpu.size), Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, Err(e) => eprintln!("{:?}", e), } @@ -595,12 +80,3 @@ pub fn run() { _ => {} }); } - -/// Grab the full path of resource-file `fname` -fn resource(rname: &str) -> String { - format!( - "{}/../layout21converters/resources/{}", - env!("CARGO_MANIFEST_DIR"), - rname - ) -} diff --git a/layout21wgpu/src/vertex.rs b/layout21wgpu/src/vertex.rs new file mode 100644 index 0000000..59cde91 --- /dev/null +++ b/layout21wgpu/src/vertex.rs @@ -0,0 +1,23 @@ +use bytemuck::{Pod, Zeroable}; + +// Local imports +use crate::Color; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct Vertex { + pub position: [f32; 2], + pub color: Color, +} +impl Vertex { + pub const ATTRIBUTES: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x3]; + + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBUTES, + } + } +} From c95405b161804dd29604ca734145eefe6604eff8 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 9 Feb 2023 10:49:53 -0800 Subject: [PATCH 27/28] Scaling, separate tessellation --- layout21wgpu/src/buffers.rs | 5 + layout21wgpu/src/gpu.rs | 16 +-- layout21wgpu/src/layout.rs | 235 ++++++++++++++++++++++-------------- layout21wgpu/src/lib.rs | 10 +- layout21wgpu/src/run.rs | 21 +++- 5 files changed, 183 insertions(+), 104 deletions(-) create mode 100644 layout21wgpu/src/buffers.rs diff --git a/layout21wgpu/src/buffers.rs b/layout21wgpu/src/buffers.rs new file mode 100644 index 0000000..aec0fb7 --- /dev/null +++ b/layout21wgpu/src/buffers.rs @@ -0,0 +1,5 @@ + +use lyon::tessellation::geometry_builder::VertexBuffers; +use crate::Vertex; + +pub type Buffers = VertexBuffers; diff --git a/layout21wgpu/src/gpu.rs b/layout21wgpu/src/gpu.rs index e2ba7c4..ce5ee78 100644 --- a/layout21wgpu/src/gpu.rs +++ b/layout21wgpu/src/gpu.rs @@ -9,7 +9,7 @@ use wgpu::util::DeviceExt; use winit::window::Window; // Local Imports -use crate::{LayoutDisplay, Vertex}; +use crate::{Buffers, Vertex}; /// # GPU Stuff /// @@ -32,7 +32,7 @@ impl GpuStuff { /// Once this gets done, things really do calm down to just the vertex and index buffers, /// and writing triangles to them. /// - pub async fn new(window: &Window, layout: &LayoutDisplay) -> Self { + pub async fn new(window: &Window, buffers: &Buffers) -> Self { let size = window.inner_size(); let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { @@ -114,12 +114,12 @@ impl GpuStuff { let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&layout.geometry.vertices), + contents: bytemuck::cast_slice(&buffers.vertices), usage: wgpu::BufferUsages::VERTEX, }); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), - contents: bytemuck::cast_slice(&layout.geometry.indices), + contents: bytemuck::cast_slice(&buffers.indices), usage: wgpu::BufferUsages::INDEX, }); @@ -145,7 +145,7 @@ impl GpuStuff { } /// Render the current frame - pub fn render(&self, max_index: u32) -> Result<(), wgpu::SurfaceError> { + pub fn render(&self, buffers: &Buffers) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; let view = output .texture @@ -180,9 +180,9 @@ impl GpuStuff { render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); render_pass.draw_indexed( - 0..max_index, // indices - 0, // base_vertex - 0..1, // instances + 0..buffers.indices.len() as u32, // indices + 0, // base_vertex + 0..1, // instances ); } diff --git a/layout21wgpu/src/layout.rs b/layout21wgpu/src/layout.rs index 6f7fc2f..faafca3 100644 --- a/layout21wgpu/src/layout.rs +++ b/layout21wgpu/src/layout.rs @@ -1,31 +1,167 @@ +use std::collections::HashMap; + use lyon::{ geom::Box2D, - math::point, + math::{point, Transform}, path::polygon::Polygon, tessellation::{ geometry_builder::{BuffersBuilder, VertexBuffers}, FillOptions, FillTessellator, FillVertex, FillVertexConstructor, }, }; -use std::collections::HashMap; use layout21protos::{self, conv as proto_converters}; use layout21raw as raw; use layout21utils::Ptr; -use log::error; + // Local imports use crate::{Color, ColorWheel, Vertex}; +/// +/// # Tessellate `layout_display` to triangles ready to render on the GPU. +/// +pub fn tessellate(layout_display: &LayoutDisplay, size: &Size) -> VertexBuffers { + let cell1 = layout_display.cell.read().unwrap(); + let layout = cell1.layout.as_ref().unwrap(); + let bbox = &layout_display.bbox; + let layer_colors = &layout_display.layer_colors; + + let screen_transform = fit(&bbox, size); + let transform = &screen_transform.transform; + + let mut tessellator = FillTessellator::new(); + let mut buffers: VertexBuffers = VertexBuffers::new(); + + // Closures to convert between lyon and layout21 shapes, applying the transform + let get_lyon_point = |p: &raw::Point| transform.transform_point(point(p.x as f32, p.y as f32)); + let get_lyon_rect = |r: &raw::Rect| Box2D::new(get_lyon_point(&r.p0), get_lyon_point(&r.p1)); + + for elem in &layout.elems { + let color = layer_colors.get(&elem.layer).unwrap().clone(); + + let shape = &elem.inner; + match shape { + raw::Shape::Rect(r) => { + tessellator + .tessellate_rectangle( + &get_lyon_rect(r), + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut buffers, WithColor::new(color)), + ) + .unwrap(); + } + raw::Shape::Path(p) => { + // Operate on manhattan paths only, for now + // If the path is non-manhattan, we'll just skip it + let path_rects = p.rects(); + if path_rects.is_none() { + continue; + } + let path_rects = path_rects.unwrap(); + + // Tessellate each rectangle in the path + for r in path_rects.iter() { + let lyon_rect = get_lyon_rect(r); + tessellator + .tessellate_rectangle( + &lyon_rect, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut buffers, WithColor::new(color)), + ) + .unwrap(); + } + } + raw::Shape::Polygon(p) => { + let points: Vec<_> = p.points.iter().map(|p| get_lyon_point(p)).collect(); + let lyon_polygon: Polygon<_> = Polygon { + points: &points, + closed: true, + }; + tessellator + .tessellate_polygon( + lyon_polygon, + &FillOptions::DEFAULT, + &mut BuffersBuilder::new(&mut buffers, WithColor::new(color)), + ) + .unwrap(); + } + } + } + + buffers +} + +/// Screen/ window size +#[derive(Debug)] +pub struct Size { + pub width: T, + pub height: T, +} +/// Get the transform to fit the bounding box in the screen +fn fit(bbox: &raw::BoundBox, size: &Size) -> ScreenTransformState { + let xspan = (bbox.p1.x - bbox.p0.x) as f64; + let yspan = (bbox.p1.y - bbox.p0.y) as f64; + + // Sort out which dimension to scale to + let zoom = if yspan * size.width as f64 > xspan * size.height as f64 { + 2.0 / yspan // scale to height + } else { + 2.0 / xspan // scale to width + }; + let zoom = (0.9 * zoom) as f32; // leave a bit of padding + + // Get the center of the bounding box + let xmid = (bbox.p1.x + bbox.p0.x) as f32 / 2.0; + let ymid = (bbox.p1.y + bbox.p0.y) as f32 / 2.0; + + // Provide a panning coordinate which scales/ zooms it into GPU coordinates + let pan = (-zoom * xmid, -zoom * ymid); + + ScreenTransformState::new(zoom, pan) +} + +/// The state of zooming and panning the screen +#[derive(Debug)] +pub struct ScreenTransformState { + pub zoom: f32, + pub pan: (f32, f32), + pub transform: Transform, +} +impl ScreenTransformState { + pub fn new(zoom: f32, pan: (f32, f32)) -> Self { + Self { + zoom, + pan, + transform: Transform::identity() + .pre_translate((pan.0, pan.1).into()) + .pre_scale(zoom, zoom), + } + } + pub fn identity() -> Self { + Self { + zoom: 1.0, + pan: (0.0, 0.0), + transform: Transform::identity(), + } + } + pub fn update(&mut self, zoom: f32, pan: (f32, f32)) { + self.zoom = zoom; + self.pan = pan; + self.transform = Transform::identity() + .pre_translate((pan.0, pan.1).into()) + .pre_scale(zoom, zoom); + } +} + #[derive(Debug)] pub struct LayoutDisplay { - // Data elements + // Source layout data pub lib: raw::Library, pub cell: Ptr, - // Rendering elements + // Derived at load time pub bbox: raw::BoundBox, pub layer_colors: HashMap, - pub geometry: VertexBuffers, } impl LayoutDisplay { pub fn from_proto() -> Self { @@ -39,102 +175,21 @@ impl LayoutDisplay { let cell1 = cell.read().unwrap(); let layout = cell1.layout.as_ref().unwrap(); - let mut layer_colors: HashMap = HashMap::new(); - - // let mut rects: Vec = Vec::with_capacity(layout.elems.len()); let bbox: raw::BoundBox = layout.bbox(); - error!("bbox: {:?}", bbox); + let mut layer_colors: HashMap = HashMap::new(); let mut color_wheel = ColorWheel::new(); - let mut tessellator = FillTessellator::new(); - let mut geometry: VertexBuffers = VertexBuffers::new(); - for elem in &layout.elems { - let color = layer_colors + layer_colors .entry(elem.layer) - .or_insert_with(|| color_wheel.next()) - .clone(); - - let shape = &elem.inner; - match shape { - raw::Shape::Rect(r) => { - let lyon_rect = Box2D::new( - point( - r.p0.x as f32 / bbox.p1.x as f32, - r.p0.y as f32 / bbox.p1.y as f32, - ), - point( - r.p1.x as f32 / bbox.p1.x as f32, - r.p1.y as f32 / bbox.p1.y as f32, - ), - ); - tessellator - .tessellate_rectangle( - &lyon_rect, - &FillOptions::DEFAULT, - &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), - ) - .unwrap(); - } - raw::Shape::Path(p) => { - let path_rects = p.rects(); - if path_rects.is_none() { - continue; - } - let path_rects = path_rects.unwrap(); - for r in path_rects.iter() { - let lyon_rect = Box2D::new( - point( - r.p0.x as f32 / bbox.p1.x as f32, - r.p0.y as f32 / bbox.p1.y as f32, - ), - point( - r.p1.x as f32 / bbox.p1.x as f32, - r.p1.y as f32 / bbox.p1.y as f32, - ), - ); - tessellator - .tessellate_rectangle( - &lyon_rect, - &FillOptions::DEFAULT, - &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), - ) - .unwrap(); - } - } - raw::Shape::Polygon(p) => { - let points: Vec<_> = p - .points - .iter() - .map(|p| { - lyon::math::Point::new( - p.x as f32 / bbox.p1.x as f32, - p.y as f32 / bbox.p1.y as f32, - ) - }) - .collect(); - let lyon_polygon: Polygon<_> = Polygon { - points: &points, - closed: true, - }; - tessellator - .tessellate_polygon( - lyon_polygon, - &FillOptions::DEFAULT, - &mut BuffersBuilder::new(&mut geometry, WithColor::new(color)), - ) - .unwrap(); - } - } + .or_insert_with(|| color_wheel.next()); } Self { lib: rawlib, cell: cell.clone(), bbox, - layer_colors, - geometry, } } } diff --git a/layout21wgpu/src/lib.rs b/layout21wgpu/src/lib.rs index 4522549..49abf67 100644 --- a/layout21wgpu/src/lib.rs +++ b/layout21wgpu/src/lib.rs @@ -1,3 +1,8 @@ +//! +//! # Layout21 WGPU +//! + +// Internal modules mod color; use crate::color::{Color, ColorWheel}; @@ -7,8 +12,11 @@ use crate::gpu::GpuStuff; mod vertex; use crate::vertex::Vertex; +mod buffers; +use crate::buffers::Buffers; + mod layout; -use crate::layout::LayoutDisplay; +use crate::layout::{tessellate, LayoutDisplay, Size}; // Primary public export: the run function mod run; diff --git a/layout21wgpu/src/run.rs b/layout21wgpu/src/run.rs index 8fe90c3..fea2ca2 100644 --- a/layout21wgpu/src/run.rs +++ b/layout21wgpu/src/run.rs @@ -6,17 +6,29 @@ use winit::{ }; // Local imports -use crate::{GpuStuff, LayoutDisplay}; +use crate::{tessellate, GpuStuff, LayoutDisplay, Size, Buffers}; +/// # Application State pub struct State { gpu: GpuStuff, layout: LayoutDisplay, + buffers: Buffers, } impl State { async fn new(window: &Window) -> Self { let layout = LayoutDisplay::from_proto(); - let gpu = GpuStuff::new(window, &layout).await; - Self { gpu, layout } + let size = window.inner_size(); + let size: Size = Size { + width: size.width, + height: size.height, + }; + let buffers = tessellate(&layout, &size); + let gpu = GpuStuff::new(window, &buffers).await; + Self { + gpu, + layout, + buffers, + } } } @@ -65,8 +77,7 @@ pub fn run() { } Event::RedrawRequested(_) => { error!("REDRAW!!!"); - let max_index = state.layout.geometry.indices.len(); - match state.gpu.render(max_index as u32) { + match state.gpu.render(&state.buffers) { Ok(_) => {} Err(wgpu::SurfaceError::Lost) => state.gpu.resize(state.gpu.size), Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, From a1b16d6b6d1dfde7e62a311a81f306af3748dfb0 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Fri, 10 Feb 2023 15:42:31 -0800 Subject: [PATCH 28/28] Initial Python RO Tests --- Tetris/tests/ro.rs | 474 ------------------------------------ Tetris/tests/test_placer.py | 12 +- Tetris/tests/test_ro.py | 468 +++++++++++++++++++++++++++++++++++ Tetris/tetris/__init__.py | 6 +- Tetris/tetris/array.py | 11 +- Tetris/tetris/library.py | 2 +- Tetris/tetris/placement.py | 6 +- Tetris/tetris/placer.py | 6 +- Tetris/tetris/separation.py | 10 +- 9 files changed, 492 insertions(+), 503 deletions(-) delete mode 100644 Tetris/tests/ro.rs create mode 100644 Tetris/tests/test_ro.py diff --git a/Tetris/tests/ro.rs b/Tetris/tests/ro.rs deleted file mode 100644 index 3a51810..0000000 --- a/Tetris/tests/ro.rs +++ /dev/null @@ -1,474 +0,0 @@ -//! -//! # Tests of one of our very favorite circuits to place in this framework! -//! - -// Local imports -use crate::abs; -use crate::array::{Array, ArrayInstance, Arrayable}; -use crate::cell::{Cell, RawLayoutPtr}; -use crate::coords::{PrimPitches, Xy}; -use crate::library::Library; -use crate::outline::Outline; -use crate::placement::{Align, Place, RelAssign}; -use crate::raw::{self, LayoutResult}; -use crate::stack::RelZ; -use crate::utils::Ptr; -use crate::{instance::Instance, layout::Layout}; - -// Test-locals -use super::{exports, resource, stacks::SampleStacks}; - -/// Create an abs unit-cell -fn abstract_unit_cell(_lib: &mut Library) -> LayoutResult> { - Ok(Ptr::new(abstract_unit()?.into())) -} -/// Create an abs unit-cell -fn abstract_unit() -> LayoutResult { - let unitsize = (18, 1); - - let unit = abs::Abstract { - name: "Wrapper".into(), - metals: 1, - outline: Outline::rect(unitsize.0, unitsize.1)?, - ports: vec![ - abs::Port { - name: "en".into(), - kind: abs::PortKind::ZTopEdge { - track: 2, - side: abs::Side::BottomOrLeft, - into: (5, RelZ::Above), - }, - }, - abs::Port { - name: "inp".into(), - kind: abs::PortKind::ZTopEdge { - track: 3, - side: abs::Side::TopOrRight, - into: (11, RelZ::Above), - }, - }, - abs::Port { - name: "out".into(), - kind: abs::PortKind::ZTopEdge { - track: 5, - side: abs::Side::TopOrRight, - into: (11, RelZ::Above), - }, - }, - ], - }; - Ok(unit) -} -/// RO, absolute-placement edition -fn ro_abs(unit: Ptr) -> LayoutResult { - let unitsize = (18, 1); - - // Create an initially empty layout - let mut ro = Layout::new( - "RO", // name - 4, // metals - Outline::rect(130, 7)?, // outline - ); - let m2xpitch = 36; - let m2botcut = 5; - let m2topcut = 7 * m2botcut + 1; - - // For each column - for x in 0..3 { - let m2track = (m2xpitch * (x + 1) - 4) as usize; - let m2entrack = (x * m2xpitch) + m2xpitch / 2; - - // For each row - for y in 0..3 { - let loc = ((2 * x + 1) * unitsize.0, (2 * (y + 1)) * unitsize.1).into(); - let inst = Instance { - inst_name: format!("inst{}{}", x, y), - cell: unit.clone(), - loc, - reflect_horiz: false, - reflect_vert: true, - }; - ro.instances.add(inst); - - // Assign the input - let m1track = (y * 12 + 9) as usize; - let m3track = m1track + x as usize; - ro.net(format!("dly{}", x)) - .at(1, m2track, m1track, RelZ::Below) - .at(2, m3track, m2track, RelZ::Below); - if x != 0 { - // Cut M3 to the *right* of the input - ro.cut(2, m3track, m2track + 1, RelZ::Below); - } else { - // Cut M3 to the *left* of the input - ro.cut(2, m3track, m2track - 1, RelZ::Below); - } - // Assign the output - let m3track = m1track + ((x + 1) % 3) as usize; - let m1track = (y * 12 + 11) as usize; - ro.net(format!("dly{}", ((x + 1) % 3))) - .at(1, m2track + 2, m1track, RelZ::Below) - .at(2, m3track, m2track + 2, RelZ::Below); - if x != 2 { - // Cut M3 to the *left* of the output - ro.cut(2, m3track, m2track + 1, RelZ::Below); - } else { - // Cut M3 to the *right* of the output - ro.cut(2, m3track, m2track + 3, RelZ::Below); - } - - // Assign the enable - let m1track = (y * 12 + 8) as usize; - let m2track = (m2entrack + y) as usize; - ro.net(format!("en{}{}", x, y)) - .at(1, m2track, m1track, RelZ::Below); - ro.cut(1, m2track, m1track + 1, RelZ::Below); // Cut just above - } - - // Make top & bottom M2 cuts - ro.cut(1, m2track, m2botcut, RelZ::Below); - ro.cut(1, m2track, m2topcut, RelZ::Below); - ro.cut(1, m2track + 2, m2botcut, RelZ::Below); - ro.cut(1, m2track + 2, m2topcut, RelZ::Below); - } - Ok(ro.into()) -} -/// RO, relative-placement edition -fn ro_rel(unit: Ptr) -> LayoutResult { - use crate::placement::{Placeable, RelativePlace, SepBy, Separation, Side}; - let unitsize = (18, 1); - - // Create an initially empty layout - let mut ro = Layout::builder() - .name("RO") - .metals(4_usize) - .outline(Outline::rect(130, 7)?) - .build()?; - let m2xpitch = 36; - let m2botcut = 5; - let m2topcut = 7 * m2botcut + 1; - - // Next-location tracker - let mut next_loc: Place> = (unitsize.0, 2 * unitsize.1).into(); - - // For each column - for x in 0..3 { - let m2track = (m2xpitch * (x + 1) - 4) as usize; - let m2entrack = (x * m2xpitch) + m2xpitch / 2; - let mut bottom_inst: Option> = None; - - // For each row - for y in 0..3 { - let inst = Instance { - inst_name: format!("inst{}{}", x, y), - cell: unit.clone(), - loc: next_loc, - reflect_horiz: false, - reflect_vert: true, - }; - let inst = ro.instances.add(inst); - if y == 0 { - bottom_inst = Some(inst.clone()); - } - - // Assign an input M2, at the center of its pin - let assn = Placeable::Assign(Ptr::new(RelAssign { - net: format!("dly{}", x), - loc: RelativePlace { - to: Placeable::Port { - inst: inst.clone(), - port: "inp".into(), - }, - side: Side::Right, - align: Align::Center, - sep: Separation::z(1), - }, - })); - ro.places.push(assn); - - // Assign an output M2, at the right-edge of the instance - let assn = Placeable::Assign(Ptr::new(RelAssign { - net: format!("dly{}", ((x + 1) % 3)), - loc: RelativePlace { - to: Placeable::Port { - inst: inst.clone(), - port: "out".into(), - }, - side: Side::Right, - align: Align::Side(Side::Right), - sep: Separation::z(1), - }, - })); - ro.places.push(assn); - - if y == 2 { - // Top of a row. Place to the right of its bottom instance. - next_loc = RelativePlace { - to: Placeable::Instance(bottom_inst.clone().unwrap()), - side: Side::Right, - align: Align::Side(Side::Bottom), // Top or Bottom both work just as well here - sep: Separation::x(SepBy::SizeOf(unit.clone())), - } - .into(); - } else { - // Place above the most-recent instance. - next_loc = RelativePlace { - to: Placeable::Instance(inst.clone()), - side: Side::Top, - align: Align::Side(Side::Left), // Left or Right both work just as well here - sep: Separation::y(SepBy::SizeOf(unit.clone())), - } - .into(); - } - - // Assign the input - let m1track = (y * 12 + 9) as usize; - let m3track = m1track + x as usize; - ro.net(format!("dly{}", x)) - .at(2, m3track, m2track, RelZ::Below); - - if x != 0 { - // Cut M3 to the *right* of the input - ro.cut(2, m3track, m2track + 1, RelZ::Below); - } else { - // Cut M3 to the *left* of the input - ro.cut(2, m3track, m2track - 1, RelZ::Below); - } - // Assign the output - let m3track = m1track + ((x + 1) % 3) as usize; - - ro.net(format!("dly{}", ((x + 1) % 3))) - .at(2, m3track, m2track + 2, RelZ::Below); - - if x != 2 { - // Cut M3 to the *left* of the output - ro.cut(2, m3track, m2track + 1, RelZ::Below); - } else { - // Cut M3 to the *right* of the output - ro.cut(2, m3track, m2track + 3, RelZ::Below); - } - - // Assign the enable - let m1track = (y * 12 + 8) as usize; - let m2track = (m2entrack + y) as usize; - ro.net(format!("en{}{}", x, y)) - .at(1, m2track, m1track, RelZ::Below); - ro.cut(1, m2track, m1track + 1, RelZ::Below); // Cut just above - } - - // Make top & bottom M2 cuts - ro.cut(1, m2track, m2botcut, RelZ::Below); - ro.cut(1, m2track, m2topcut, RelZ::Below); - ro.cut(1, m2track + 2, m2botcut, RelZ::Below); - ro.cut(1, m2track + 2, m2topcut, RelZ::Below); - } - Ok(ro.into()) -} -/// Test importing and wrapping an existing GDSII into a [Library]/[Cell] -#[test] -fn wrap_gds() -> LayoutResult<()> { - let mut lib = Library::new("wrap_gds"); - _wrap_gds(&mut lib)?; - exports(lib, SampleStacks::pdka()?) -} -/// Most internal implementation of the `wrap_gds` test -fn _wrap_gds(lib: &mut Library) -> LayoutResult> { - // Import a [GdsLibrary] to a [raw::Library] - let gds_fname = resource("ginv.gds"); - let gds = raw::gds::gds21::GdsLibrary::load(&gds_fname)?; - - let stack = SampleStacks::pdka()?; - - let rawlib = raw::Library::from_gds(&gds, Some(Ptr::clone(&stack.rawlayers.unwrap())))?; - assert_eq!(rawlib.cells.len(), 1); - // Get a [Ptr] to the first (and only) cell - let cell = rawlib.cells.first().unwrap().clone(); - - // Add tracking of our dependence on the [raw::Library] - let rawlibptr = lib.add_rawlib(rawlib); - // Create a [Cell] from the [raw::Library]'s sole cell - let unitsize = (18, 1); - let wrapped = RawLayoutPtr { - outline: Outline::rect(unitsize.0, unitsize.1)?, // outline - metals: 1, - lib: rawlibptr, - cell, - }; - let wrapped = lib.cells.insert(wrapped); - - // Create a wrapper cell - let mut wrapper = Layout::new( - "Wrapper", // name - 1, // metals - Outline::rect(unitsize.0, unitsize.1)?, // outline - ); - wrapper.instances.add(Instance { - inst_name: "wrapped".into(), - cell: wrapped, - loc: (0, 0).into(), - reflect_horiz: false, - reflect_vert: false, - }); - // Convert the layout to a [Cell] - let mut wrapper: Cell = wrapper.into(); - // And add an [Abstract] view - wrapper.abs = Some(abstract_unit()?); - // Finally add the wrapper [Cell] to our [Library], and return a pointer to it. - let wrapper = lib.cells.insert(wrapper); - Ok(wrapper) -} -/// RO, array-placement edition -fn ro_array(unit: Ptr) -> LayoutResult { - use crate::placement::{Placeable, SepBy, Separation}; - let unitsize = (18, 1); - - // Create an initially empty layout - let mut ro = Layout::new( - "RO", // name - 4, // metals - Outline::rect(130, 7)?, // outline - ); - let m2xpitch = 36; - let m2botcut = 5; - let m2topcut = 7 * m2botcut + 1; - - // Create the main array of Instances - let a = Placeable::Array(Ptr::new(ArrayInstance { - name: "insts".into(), - loc: (unitsize.0, 6 * unitsize.1).into(), - reflect_vert: true, - reflect_horiz: false, - array: Ptr::new(Array { - name: "col".into(), - count: 3, - sep: Separation::y(SepBy::UnitSpeced(PrimPitches::y(2).into())), - unit: Arrayable::Array(Ptr::new(Array { - name: "row".into(), - unit: Arrayable::Instance(unit.clone()), - count: 3, - sep: Separation::x(SepBy::UnitSpeced(PrimPitches::x(36).into())), // FIXME! - })), - }), - })); - ro.places.push(a); - - // Now do all of the metal-layer stuff: assignments and cuts - // This part remains in absolute coordinates - - // For each column - for x in 0..3 { - let m2track = (m2xpitch * (x + 1) - 4) as usize; - let m2entrack = (x * m2xpitch) + m2xpitch / 2; - - // For each row - for y in 0..3 { - /* - # Remaining TODO here: - * Assignments, for input, output, and enable of each instance - * Cuts - let mut group = Group::new("???"); - let instptr = group.add(Instance { - inst_name: "inst".into(), - cell: unit.clone(), - loc: Place::origin(), - reflect_horiz: false, - reflect_vert: true, - }); - let a1 = group.add(AssignPlace { - net: format!("dly{}", x), - at: RelSomething( - inst, - "out", // ? - Separation::z(1), // One layer up - Align::Center, // Align on pin-center - ), - }); - AssignSomething { - net: format!("dly{}", x), - at: RelSomething( - a1, // Relative to the last assignment - Separation::z(2), // Now *two* layers up - ), - }; - */ - // Assign the input - let m1track = (y * 12 + 9) as usize; - let m3track = m1track + x as usize; - ro.net(format!("dly{}", x)) - .at(1, m2track, m1track, RelZ::Below) - .at(2, m3track, m2track, RelZ::Below); - if x != 0 { - // Cut M3 to the *right* of the input - ro.cut(2, m3track, m2track + 1, RelZ::Below); - } else { - // Cut M3 to the *left* of the input - ro.cut(2, m3track, m2track - 1, RelZ::Below); - } - // Assign the output - let m3track = m1track + ((x + 1) % 3) as usize; - let m1track = (y * 12 + 11) as usize; - ro.net(format!("dly{}", ((x + 1) % 3))) - .at(1, m2track + 2, m1track, RelZ::Below) - .at(2, m3track, m2track + 2, RelZ::Below); - if x != 2 { - // Cut M3 to the *left* of the output - ro.cut(2, m3track, m2track + 1, RelZ::Below); - } else { - // Cut M3 to the *right* of the output - ro.cut(2, m3track, m2track + 3, RelZ::Below); - } - - // Assign the enable - let m1track = (y * 12 + 8) as usize; - let m2track = (m2entrack + y) as usize; - ro.net(format!("en{}{}", x, y)) - .at(1, m2track, m1track, RelZ::Below); - ro.cut(1, m2track, m1track + 1, RelZ::Below); // Cut just above - } - - // Make top & bottom M2 cuts - ro.cut(1, m2track, m2botcut, RelZ::Below); - ro.cut(1, m2track, m2topcut, RelZ::Below); - ro.cut(1, m2track + 2, m2botcut, RelZ::Below); - ro.cut(1, m2track + 2, m2topcut, RelZ::Below); - } - Ok(ro.into()) -} -/// Runner for each of these RO tests. -/// Accepts function-arguments for the unit-cell and wrapper-cell factories. -fn _ro_test( - libname: &str, - unitfn: fn(&mut Library) -> LayoutResult>, - wrapfn: fn(Ptr) -> LayoutResult, -) -> LayoutResult<()> { - let mut lib = Library::new(libname); - let unit = unitfn(&mut lib)?; // Create the unit cell - let ro = wrapfn(unit)?; // Create the RO level - lib.cells.insert(ro); // And add it to the Library - exports(lib, SampleStacks::pdka()?) // And export everything to our handful of formats -} -// Execute a bunch of combinations, each as a separate test -#[test] -fn ro_wrap_gds_abs() -> LayoutResult<()> { - _ro_test("RoWrapGdsAbs", _wrap_gds, ro_abs) -} -#[test] -fn ro_wrap_gds_rel() -> LayoutResult<()> { - _ro_test("RoWrapGdsRel", _wrap_gds, ro_rel) -} -#[test] -fn ro_wrap_gds_array() -> LayoutResult<()> { - _ro_test("RoWrapGdsArray", _wrap_gds, ro_array) -} -#[test] -fn ro_abs_abs() -> LayoutResult<()> { - _ro_test("RoAbsAbs", abstract_unit_cell, ro_abs) -} -#[test] -fn ro_abs_rel() -> LayoutResult<()> { - _ro_test("RoAbsRel", abstract_unit_cell, ro_rel) -} -#[test] -fn ro_abs_array() -> LayoutResult<()> { - _ro_test("RoAbsArray", abstract_unit_cell, ro_rel) -} diff --git a/Tetris/tests/test_placer.py b/Tetris/tests/test_placer.py index d2f22a7..88abc38 100644 --- a/Tetris/tests/test_placer.py +++ b/Tetris/tests/test_placer.py @@ -14,6 +14,7 @@ # Non-public imports from tetris.placer import Placer + # from .tests import exports, SampleStacks @@ -315,17 +316,6 @@ def get(cls) -> "SampleLib": lil = Cell("lil") lil.layout = Layout("lil", 1, Outline.rect(2, 1)) lil.abs = Abstract(name="lil", metals=1, outline=Outline.rect(2, 1), ports=[]) - # lil_abs.ports.append( - # Port( - # name="PPP", - # kind=PortKind.ZTopEdge( - # track=0, - # side=abs.Side.BottomOrLeft, - # into=(2, stack.RelZ.Above), - # ), - # ) - # ) - # lil.abs = Some(lil_abs) lil = lib.add_cell(lil) return SampleLib( diff --git a/Tetris/tests/test_ro.py b/Tetris/tests/test_ro.py new file mode 100644 index 0000000..dfaeb74 --- /dev/null +++ b/Tetris/tests/test_ro.py @@ -0,0 +1,468 @@ +""" +# +# Tests of one of our very favorite circuits to place in this framework! +# +""" +import pytest +from typing import Optional, Callable + +# Local imports +import tetris as t + +# Test-locals +# from . import exports, resource, stacks.SampleStacks + +# Create an abs unit-cell +def abstract_unit_cell(_lib: t.Library) -> t.Cell: + return abstract_unit() + + +# Create an abs unit-cell +def abstract_unit() -> t.abstract.Abstract: + unitsize = (18, 1) + + unit = t.abstract.Abstract( + name="Wrapper", + metals=1, + outline=t.Outline.rect(unitsize[0], unitsize[1]), + ports=[ + t.abstract.Port( + name="en", + kind=t.abstract.ZTopEdge( + track=2, + side=t.abstract.Side.BottomOrLeft, + into=(5, t.RelZ.Above), + ), + ), + t.abstract.Port( + name="inp", + kind=t.abstract.ZTopEdge( + track=3, + side=t.abstract.Side.TopOrRight, + into=(11, t.RelZ.Above), + ), + ), + t.abstract.Port( + name="out", + kind=t.abstract.ZTopEdge( + track=5, + side=t.abstract.Side.TopOrRight, + into=(11, t.RelZ.Above), + ), + ), + ], + ) + return unit + + +# RO, absolute-placement edition +def ro_abs(unit: t.Cell) -> t.Layout: + unitsize = (18, 1) + + # Create an initially empty layout + ro = t.Layout( + "RO", # name + 4, # metals + t.Outline.rect(130, 7), # outline + ) + m2xpitch = 36 + m2botcut = 5 + m2topcut = 7 * m2botcut + 1 + + # For each column + for x in range(3): + m2track = m2xpitch * (x + 1) - 4 + m2entrack = (x * m2xpitch) + m2xpitch / 2 + + # For each row + for y in range(3): + loc = ((2 * x + 1) * unitsize[0], (2 * (y + 1)) * unitsize[1]) + inst = t.Instance( + name=f"inst{x}{y}", + of=unit, + loc=loc, + reflect=t.Reflect(horiz=False, vert=True), + ) + ro.add_instance(inst) + + # Assign the input + m1track = y * 12 + 9 + m3track = m1track + x + ro.net(f"dly{x}").at(1, m2track, m1track, t.RelZ.Below).at( + 2, m3track, m2track, t.RelZ.Below + ) + if x != 0: + # Cut M3 to the *right* of the input + ro.cut(2, m3track, m2track + 1, t.RelZ.Below) + else: + # Cut M3 to the *left* of the input + ro.cut(2, m3track, m2track - 1, t.RelZ.Below) + + # Assign the output + m3track = m1track + ((x + 1) % 3) + m1track = y * 12 + 11 + ro.net(f"dly{((x + 1) % 3)}").at(1, m2track + 2, m1track, t.RelZ.Below).at( + 2, m3track, m2track + 2, t.RelZ.Below + ) + if x != 2: + # Cut M3 to the *left* of the output + ro.cut(2, m3track, m2track + 1, t.RelZ.Below) + else: + # Cut M3 to the *right* of the output + ro.cut(2, m3track, m2track + 3, t.RelZ.Below) + + # Assign the enable + m1track = y * 12 + 8 + m2track = m2entrack + y + ro.net(f"en{x}{y}").at(1, m2track, m1track, t.RelZ.Below) + ro.cut(1, m2track, m1track + 1, t.RelZ.Below) # Cut just above + + # Make top & bottom M2 cuts + ro.cut(1, m2track, m2botcut, t.RelZ.Below) + ro.cut(1, m2track, m2topcut, t.RelZ.Below) + ro.cut(1, m2track + 2, m2botcut, t.RelZ.Below) + ro.cut(1, m2track + 2, m2topcut, t.RelZ.Below) + + return ro + + +def ro_rel(unit: t.Cell) -> t.Layout: + """# RO, relative-placement edition""" + + unitsize = (18, 1) + + # Create an initially empty layout + ro = t.Layout(name="RO", metals=4, outline=t.Outline.rect(130, 7)) + m2xpitch = 36 + m2botcut = 5 + m2topcut = 7 * m2botcut + 1 + + # Next-location tracker + next_loc: t.Place = t.AbsPlace(xy=t.Xy.new(unitsize[0], 2 * unitsize[1])) + + # For each column + for x in range(3): + m2track = m2xpitch * (x + 1) - 4 + m2entrack = (x * m2xpitch) + m2xpitch / 2 + bottom_inst: Optional[t.Instance] = None + + # For each row + for y in range(3): + inst = t.Instance( + name=f"inst{x}{y}", + of=unit, + loc=next_loc, + reflect=t.Reflect(horiz=False, vert=True), + ) + inst = ro.add_instance(inst) + if y == 0: + bottom_inst = inst + + # FIXME! relative net-assignment locations + # # Assign an input M2, at the center of its pin + # assn = Placeable.Assign(Ptr.new(RelAssign + # net: format!("dly", x), + # loc: RelativePlace + # to: Placeable.Port + # inst: inst, + # port: "inp", + # , + # side: Side.Right, + # align: Align.Center, + # sep: Separation.by_z(1), + # , + # )) + # ro.places.push(assn) + + # # Assign an output M2, at the right-edge of the instance + # assn = Placeable.Assign(Ptr.new(RelAssign + # net: format!("dly", ((x + 1) % 3)), + # loc: RelativePlace + # to: Placeable.Port + # inst: inst, + # port: "out", + # , + # side: Side.Right, + # align: Align.Side(Side.Right), + # sep: Separation.by_z(1), + # , + # )) + # ro.places.push(assn) + + if y == 2: + # Top of a row. Place to the right of its bottom instance. + next_loc = t.RelativePlace( + to=bottom_inst, + side=t.Side.Right, + align=t.AlignSide( + t.Side.Bottom + ), # Top or Bottom both work just as well here + sep=t.Separation.by_x(t.PrimPitches.x(unitsize[0])), + ) + + else: + # Place above the most-recent instance. + next_loc = t.RelativePlace( + to=inst, + side=t.Side.Top, + align=t.AlignSide( + t.Side.Left + ), # Left or Right both work just as well here + sep=t.Separation.by_y(t.PrimPitches.y(unitsize[1])), + ) + + # Assign the input + m1track = y * 12 + 9 + m3track = m1track + x + ro.net(f"dly{x}").at(2, m3track, m2track, t.RelZ.Below) + + if x != 0: + # Cut M3 to the *right* of the input + ro.cut(2, m3track, m2track + 1, t.RelZ.Below) + else: + # Cut M3 to the *left* of the input + ro.cut(2, m3track, m2track - 1, t.RelZ.Below) + + # Assign the output + m3track = m1track + ((x + 1) % 3) + + ro.net(f"dly{ (x + 1) % 3}").at(2, m3track, m2track + 2, t.RelZ.Below) + + if x != 2: + # Cut M3 to the *left* of the output + ro.cut(2, m3track, m2track + 1, t.RelZ.Below) + else: + # Cut M3 to the *right* of the output + ro.cut(2, m3track, m2track + 3, t.RelZ.Below) + + # Assign the enable + m1track = y * 12 + 8 + m2track = m2entrack + y + ro.net(f"en{x}{y}").at(1, m2track, m1track, t.RelZ.Below) + ro.cut(1, m2track, m1track + 1, t.RelZ.Below) # Cut just above + + # Make top & bottom M2 cuts + ro.cut(1, m2track, m2botcut, t.RelZ.Below) + ro.cut(1, m2track, m2topcut, t.RelZ.Below) + ro.cut(1, m2track + 2, m2botcut, t.RelZ.Below) + ro.cut(1, m2track + 2, m2topcut, t.RelZ.Below) + + return ro + + +def ro_array(unit: t.Cell) -> t.Layout: + """# RO, array-placement edition""" + + unitsize = (18, 1) + + # Create an initially empty layout + ro = t.Layout.new( + "RO", # name + 4, # metals + t.Outline.rect(130, 7), # outline + ) + m2xpitch = 36 + m2botcut = 5 + m2topcut = 7 * m2botcut + 1 + + # Create the main array of t.Instances + array_inst = t.Instance( + name="array_inst", + unit=t.Array( + name="col", + count=3, + sep=t.Separation.by_y(t.PrimPitches.y(2)), + unit=t.Array( + name="row", + unit=unit, + count=3, + sep=t.Separation.by_x(t.PrimPitches.x(36)), # FIXME! + ), + reflect=t.Reflect(vert=True, horiz=False), + ), + loc=(unitsize[0], 6 * unitsize[1]), + ) + ro.add_instance(array_inst) + + # # Now do all of the metal-layer stuff: assignments and cuts + # # This part remains in absolute coordinates + + # # For each column + # for x in 0..3 + # m2track = (m2xpitch * (x + 1) - 4) + # m2entrack = (x * m2xpitch) + m2xpitch / 2 + + # # For each row + # for y in 0..3 + # /* + # # Remaining TODO here: + # * Assignments, for input, output, and enable of each instance + # * Cuts + # group = Group.new("") + # instptr = group.add(t.Instance + # name: "inst", + # cell: unit, + # loc: Place.origin(), + # reflect_horiz: False, + # reflect_vert: True, + # ) + # a1 = group.add(AssignPlace + # net: format!("dly", x), + # at: RelSomething( + # inst, + # "out", # + # Separation.by_z(1), # One layer up + # Align.Center, # Align on pin-center + # ), + # ) + # AssignSomething + # net: format!("dly", x), + # at: RelSomething( + # a1, # Relative to the last assignment + # Separation.by_z(2), # Now *two* layers up + # ), + + # */ + # # Assign the input + # m1track = (y * 12 + 9) + # m3track = m1track + x + # ro.net(format!("dly", x)) + # .at(1, m2track, m1track,t.RelZ.Below) + # .at(2, m3track, m2track,t.RelZ.Below) + # if x != 0 + # # Cut M3 to the *right* of the input + # ro.cut(2, m3track, m2track + 1,t.RelZ.Below) + # else + # # Cut M3 to the *left* of the input + # ro.cut(2, m3track, m2track - 1,t.RelZ.Below) + + # # Assign the output + # m3track = m1track + ((x + 1) % 3) + # m1track = (y * 12 + 11) + # ro.net(format!("dly", ((x + 1) % 3))) + # .at(1, m2track + 2, m1track,t.RelZ.Below) + # .at(2, m3track, m2track + 2,t.RelZ.Below) + # if x != 2 + # # Cut M3 to the *left* of the output + # ro.cut(2, m3track, m2track + 1,t.RelZ.Below) + # else + # # Cut M3 to the *right* of the output + # ro.cut(2, m3track, m2track + 3,t.RelZ.Below) + + # # Assign the enable + # m1track = (y * 12 + 8) + # m2track = (m2entrack + y) + # ro.net(format!("en", x, y)) + # .at(1, m2track, m1track,t.RelZ.Below) + # ro.cut(1, m2track, m1track + 1,t.RelZ.Below) # Cut just above + + # # Make top & bottom M2 cuts + # ro.cut(1, m2track, m2botcut,t.RelZ.Below) + # ro.cut(1, m2track, m2topcut,t.RelZ.Below) + # ro.cut(1, m2track + 2, m2botcut,t.RelZ.Below) + # ro.cut(1, m2track + 2, m2topcut,t.RelZ.Below) + + return ro + + +# # Test importing and wrapping an existing GDSII into a [Library]/[t.Cell] + +# def wrap_gds() -> None: +# lib =t.Library("wrap_gds") +# _wrap_gds( lib) +# exports(lib, SampleStacks.pdka()) + +# # Most internal implementation of the `wrap_gds` test +# def _wrap_gds(lib: t.Library) ->t.LayoutResult> +# # Import a [GdsLibrary] to a [raw.Library] +# gds_fname = resource("ginv.gds") +# gds = raw.gds.gds21.GdsLibrary.load(&gds_fname) + +# stack = SampleStacks.pdka() + +# rawlib = raw.Library.from_gds(&gds, Some(Ptr.clone(&stack.rawlayers.unwrap()))) +# assert_eq!(rawlib.cells.len(), 1) +# # Get a [Ptr] to the first (and only) cell +# cell = rawlib.cells.first().unwrap() + +# # Add tracking of our dependence on the [raw.Library] +# rawlibptr = lib.add_rawlib(rawlib) +# # Create a [t.Cell] from the [raw.Library]'s sole cell +# unitsize = (18, 1) +# wrapped = RawLayoutPtr +# outline:t.Outline.rect(unitsize[0], unitsize[1]), # outline +# metals: 1, +# lib: rawlibptr, +# cell, + +# wrapped = lib.cells.insert(wrapped) + +# # Create a wrapper cell +# wrapper =t.Layout.new( +# "Wrapper", # name +# 1, # metals +# t.Outline.rect(unitsize[0], unitsize[1]), # outline +# ) +# wrapper.add_instance(t.Instance +# name: "wrapped", +# cell: wrapped, +# loc: (0, 0), +# reflect_horiz: False, +# reflect_vert: False, +# ) +# # Convert the layout to a [t.Cell] +# wrapper: t.Cell = wrapper +# # And add an [Abstract] view +# wrapper.abs = Some(abstract_unit()) +# # Finally add the wrapper [t.Cell] to our [Library], and return a pointer to it. +# wrapper = lib.cells.insert(wrapper) +# return wrapper + + +def _ro_test( + libname: str, + unitfn: Callable, # def (t.Library) -> t.Cell, + wrapfn: Callable, # def (t.Cell) -> t.Layout, +) -> None: + """# Runner for each of these RO tests. + Accepts function-arguments for the unit-cell and wrapper-cell factories.""" + + lib = t.Library(libname) + unit = unitfn(lib) # Create the unit cell + ro = wrapfn(unit) # Create the RO layout + ro_cell = t.Cell(name="RO", layout=ro) # Wrap it in a Cell + lib.add_cell(ro_cell) # And add it to the Library + exports(lib, SampleStacks.pdka()) # And export everything to our handful of formats + + +""" +# Execute a bunch of combinations, each as a separate test +""" + + +@pytest.mark.xfail +def test_ro_wrap_gds_abs() -> None: + return _ro_test("RoWrapGdsAbs", _wrap_gds, ro_abs) + + +@pytest.mark.xfail +def test_ro_wrap_gds_rel() -> None: + return _ro_test("RoWrapGdsRel", _wrap_gds, ro_rel) + + +@pytest.mark.xfail +def test_ro_wrap_gds_array() -> None: + return _ro_test("RoWrapGdsArray", _wrap_gds, ro_array) + + +def test_ro_abs_abs() -> None: + return _ro_test("RoAbsAbs", abstract_unit_cell, ro_abs) + + +def test_ro_abs_rel() -> None: + return _ro_test("RoAbsRel", abstract_unit_cell, ro_rel) + + +@pytest.mark.xfail +def test_ro_abs_array() -> None: + return _ro_test("RoAbsArray", abstract_unit_cell, ro_array) diff --git a/Tetris/tetris/__init__.py b/Tetris/tetris/__init__.py index ecd4cd9..b476a30 100644 --- a/Tetris/tetris/__init__.py +++ b/Tetris/tetris/__init__.py @@ -17,9 +17,9 @@ from .array import * from .group import * from .instantiable import * -from .reflect import * -from .align import * -from .separation import * +from .reflect import * +from .align import * +from .separation import * # from .layer_period import * # from .tracks import * diff --git a/Tetris/tetris/array.py b/Tetris/tetris/array.py index ba7a196..7faa5b6 100644 --- a/Tetris/tetris/array.py +++ b/Tetris/tetris/array.py @@ -4,9 +4,10 @@ # Uniformly-spaced repetitions of [Arrayable] elements. # -from dataclasses import dataclass +from dataclasses import dataclass, field # Local imports +from .reflect import Reflect from .coords import PrimPitches, Xy from .separation import Separation @@ -20,15 +21,21 @@ class Array: # Array Name name: str + # Unit to be Arrayed unit: "Instantiable" + # Number of elements count: int + # Separation between elements # FIXME: whether to include the size of the element or not sep: Separation + # Reflection state of the unit element + reflect: Reflect = field(default_factory=Reflect.default) + # Size of the Array's rectangular `boundbox` i.e. the zero-origin `boundbox` of its `cell`. def boundbox_size(self) -> Xy[PrimPitches]: - _unit = self.unit.boundbox_size() raise NotImplementedError # FIXME! + _unit = self.unit.boundbox_size() diff --git a/Tetris/tetris/library.py b/Tetris/tetris/library.py index 7d8798f..01beec2 100644 --- a/Tetris/tetris/library.py +++ b/Tetris/tetris/library.py @@ -33,8 +33,8 @@ class Library: # def add_rawlib(self, rawlib: raw.Library) -> Ptr : # self.rawlibs.add(rawlib) - # Add a [Cell] def add_cell(self, cell: Cell) -> Cell: + """# Add a [Cell]""" if not isinstance(cell, Cell): raise TypeError if cell.name in self.cells: diff --git a/Tetris/tetris/placement.py b/Tetris/tetris/placement.py index e7222fb..382c1ad 100644 --- a/Tetris/tetris/placement.py +++ b/Tetris/tetris/placement.py @@ -43,13 +43,11 @@ def loc(self: Placeable) -> "Place": raise TypeError - - @dataclass class AbsPlace: """Absolute-Valued Placement, in Primitive Pitches""" - xy: Xy ## FIXME: [PrimPitches] + xy: Xy ## FIXME: [PrimPitches] @staticmethod def origin() -> "AbsPlace": @@ -82,6 +80,7 @@ class RelativePlace: resolved: Optional[AbsPlace] = None + # # Placement Union # # Includes absolute and relative placements. @@ -91,4 +90,3 @@ class RelativePlace: # which can be specified relative to any other [Placeable] object. # Place = Union[AbsPlace, RelativePlace] - diff --git a/Tetris/tetris/placer.py b/Tetris/tetris/placer.py index 2f1a5f0..071ef63 100644 --- a/Tetris/tetris/placer.py +++ b/Tetris/tetris/placer.py @@ -237,7 +237,7 @@ def resolve_instance_place(self, inst: Instance) -> AbsPlace: top = place_relative_to_this_bbox.bottom else: raise ValueError - + if inst.loc.align.side == Side.Left: left = place_relative_to_this_bbox.left elif inst.loc.align.side == Side.Right: @@ -248,7 +248,7 @@ def resolve_instance_place(self, inst: Instance) -> AbsPlace: bottom = place_relative_to_this_bbox.bottom else: raise ValueError - + origin = something(inst=inst, top=top, bottom=bottom, left=left, right=right) return AbsPlace(origin) @@ -259,7 +259,7 @@ def something( bottom: Optional[PrimPitches], left: Optional[PrimPitches], right: Optional[PrimPitches], -)-> Xy: +) -> Xy: # What we know at this point: # * The cell's bounding box # * *Either* the top or bottom edge of the instance diff --git a/Tetris/tetris/separation.py b/Tetris/tetris/separation.py index 468d041..90f5c7e 100644 --- a/Tetris/tetris/separation.py +++ b/Tetris/tetris/separation.py @@ -5,15 +5,15 @@ # Local imports from .coords import UnitSpeced, Dir - -@dataclass -class Instantiable: - ... # FIXME! +# from .instantiable import Instantiable @dataclass class SizeOf: - of: Instantiable + """# The size of `of`.""" + + ... # FIXME! + # of: "Instantiable" # Enumerated means of specifying x-y relative-placement separation