From 565f116c29faaa67e396a9b6750f0b8b453528c6 Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sat, 30 Dec 2023 12:35:23 -0800 Subject: [PATCH 1/8] add sky130 pmos tile --- pdks/sky130pdk/src/atoll/mod.rs | 178 ++++++++++++++++++++++++++++---- tests/src/atoll/mod.rs | 53 ++++++++-- 2 files changed, 203 insertions(+), 28 deletions(-) diff --git a/pdks/sky130pdk/src/atoll/mod.rs b/pdks/sky130pdk/src/atoll/mod.rs index bcae4410..10748aca 100644 --- a/pdks/sky130pdk/src/atoll/mod.rs +++ b/pdks/sky130pdk/src/atoll/mod.rs @@ -126,7 +126,7 @@ pub struct MosTileIo { pub b: InOut, } -/// A tile containing a set of NMOS transistors. +/// A tile containing a set of MOS transistors. /// /// There are `nf` transistors, each of length `len` and width `w`. /// The gates of all transistors are connected. @@ -134,21 +134,28 @@ pub struct MosTileIo { /// /// This tile does not contain internal taps. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] -pub struct NmosTile { +struct MosTile { /// Transistor width. - pub w: i64, + w: i64, /// Transistor length. - pub len: MosLength, + len: MosLength, /// Number of fingers. - pub nf: i64, + nf: i64, } -impl Block for NmosTile { +impl MosTile { + /// Create a new MOS tile with the given physical transistor dimensions. + fn new(w: i64, len: MosLength, nf: i64) -> Self { + Self { w, len, nf } + } +} + +impl Block for MosTile { type Io = MosTileIo; fn id() -> ArcStr { - arcstr::literal!("nmos_tile") + arcstr::literal!("mos_tile") } fn name(&self) -> ArcStr { @@ -164,11 +171,11 @@ impl Block for NmosTile { } } -impl ExportsLayoutData for NmosTile { +impl ExportsLayoutData for MosTile { type LayoutData = (); } -impl Layout for NmosTile { +impl Layout for MosTile { fn layout( &self, io: &mut substrate::io::layout::Builder, @@ -176,7 +183,6 @@ impl Layout for NmosTile { ) -> substrate::error::Result { let stack = cell.ctx.get_installation::>().unwrap(); let grid = RoutingGrid::new((*stack).clone(), 0..2); - let slice = stack.slice(0..2); let tracks = (0..self.nf + 1) .map(|i| { @@ -248,21 +254,15 @@ impl Layout for NmosTile { cell.draw(Shape::new(cell.ctx.layers.licon1, cut))?; } - let bbox = cell.bbox().unwrap(); - let lcm_bbox = slice.lcm_to_physical_rect(slice.expand_to_lcm_units(bbox)); - cell.draw(Shape::new(cell.ctx.layers.nsdm, lcm_bbox))?; - cell.draw(Shape::new(cell.ctx.layers.pwell, lcm_bbox))?; - io.b.push(IoShape::with_layers(cell.ctx.layers.pwell, lcm_bbox)); - Ok(()) } } -impl ExportsNestedData for NmosTile { +impl ExportsNestedData for MosTile { type NestedData = (); } -impl Schematic for NmosTile { +impl Schematic for MosTile { fn schematic( &self, io: &substrate::io::schematic::Bundle, @@ -286,3 +286,145 @@ impl Schematic for NmosTile { Ok(()) } } + +/// A tile containing a set of NMOS transistors. +/// +/// See [`MosTile`] for more information. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub struct NmosTile { + tile: MosTile, +} + +impl NmosTile { + /// Create a new NMOS tile with the given physical transistor dimensions. + pub fn new(w: i64, len: MosLength, nf: i64) -> Self { + Self { + tile: MosTile::new(w, len, nf), + } + } +} + +impl Block for NmosTile { + type Io = MosTileIo; + fn id() -> ArcStr { + arcstr::literal!("nmos_tile") + } + + fn name(&self) -> ArcStr { + arcstr::format!("n{}", self.tile.name()) + } + + fn io(&self) -> Self::Io { + self.tile.io() + } +} + +impl ExportsLayoutData for NmosTile { + type LayoutData = (); +} + +impl Layout for NmosTile { + fn layout( + &self, + io: &mut substrate::io::layout::Builder, + cell: &mut CellBuilder, + ) -> substrate::error::Result { + let stack = cell.ctx.get_installation::>().unwrap(); + let slice = stack.slice(0..2); + self.tile.layout(io, cell)?; + + let bbox = cell.bbox().unwrap(); + let lcm_bbox = slice.lcm_to_physical_rect(slice.expand_to_lcm_units(bbox)); + cell.draw(Shape::new(cell.ctx.layers.nsdm, lcm_bbox))?; + cell.draw(Shape::new(cell.ctx.layers.pwell, lcm_bbox))?; + io.b.push(IoShape::with_layers(cell.ctx.layers.pwell, lcm_bbox)); + + Ok(()) + } +} + +impl ExportsNestedData for NmosTile { + type NestedData = (); +} + +impl Schematic for NmosTile { + fn schematic( + &self, + io: &substrate::io::schematic::Bundle, + cell: &mut substrate::schematic::CellBuilder, + ) -> substrate::error::Result { + cell.instantiate_connected(self.tile.clone(), io); + Ok(()) + } +} + +/// A tile containing a set of PMOS transistors. +/// +/// See [`MosTile`] for more information. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub struct PmosTile { + tile: MosTile, +} + +impl PmosTile { + /// Create a new PMOS tile with the given physical transistor dimensions. + pub fn new(w: i64, len: MosLength, nf: i64) -> Self { + Self { + tile: MosTile::new(w, len, nf), + } + } +} + +impl Block for PmosTile { + type Io = MosTileIo; + fn id() -> ArcStr { + arcstr::literal!("pmos_tile") + } + + fn name(&self) -> ArcStr { + arcstr::format!("p{}", self.tile.name()) + } + + fn io(&self) -> Self::Io { + self.tile.io() + } +} + +impl ExportsLayoutData for PmosTile { + type LayoutData = (); +} + +impl Layout for PmosTile { + fn layout( + &self, + io: &mut substrate::io::layout::Builder, + cell: &mut CellBuilder, + ) -> substrate::error::Result { + let stack = cell.ctx.get_installation::>().unwrap(); + let slice = stack.slice(0..2); + + self.tile.layout(io, cell)?; + + let bbox = cell.bbox().unwrap(); + let lcm_bbox = slice.lcm_to_physical_rect(slice.expand_to_lcm_units(bbox)); + cell.draw(Shape::new(cell.ctx.layers.psdm, lcm_bbox))?; + cell.draw(Shape::new(cell.ctx.layers.nwell, lcm_bbox))?; + io.b.push(IoShape::with_layers(cell.ctx.layers.pwell, lcm_bbox)); + + Ok(()) + } +} + +impl ExportsNestedData for PmosTile { + type NestedData = (); +} + +impl Schematic for PmosTile { + fn schematic( + &self, + io: &substrate::io::schematic::Bundle, + cell: &mut substrate::schematic::CellBuilder, + ) -> substrate::error::Result { + self.tile.schematic(io, cell) + } +} diff --git a/tests/src/atoll/mod.rs b/tests/src/atoll/mod.rs index b9ec8efc..8c7b4bfb 100644 --- a/tests/src/atoll/mod.rs +++ b/tests/src/atoll/mod.rs @@ -35,11 +35,7 @@ fn sky130_atoll_nmos_tile() { let netlist_path = get_path("sky130_atoll_nmos_tile", "schematic.sp"); let ctx = sky130_open_ctx(); - let block = sky130pdk::atoll::NmosTile { - w: 1_680, - nf: 3, - len: MosLength::L150, - }; + let block = sky130pdk::atoll::NmosTile::new(1_680, MosLength::L150, 3); ctx.write_layout(block, gds_path) .expect("failed to write layout"); @@ -74,6 +70,47 @@ fn sky130_atoll_nmos_tile() { .expect("failed to write abstract"); } +#[test] +fn sky130_atoll_pmos_tile() { + let gds_path = get_path("sky130_atoll_pmos_tile", "layout.gds"); + let abs_path = get_path("sky130_atoll_pmos_tile", "abs.gds"); + let netlist_path = get_path("sky130_atoll_pmos_tile", "schematic.sp"); + let ctx = sky130_open_ctx(); + + let block = sky130pdk::atoll::PmosTile::new(1_680, MosLength::L150, 3); + + ctx.write_layout(block, gds_path) + .expect("failed to write layout"); + + let scir = ctx + .export_scir(block) + .unwrap() + .scir + .convert_schema::() + .unwrap() + .convert_schema::() + .unwrap() + .build() + .unwrap(); + Spice + .write_scir_netlist_to_file(&scir, netlist_path, NetlistOptions::default()) + .expect("failed to write netlist"); + + let handle = ctx.generate_layout(block); + + let stack = ctx.get_installation::>().unwrap(); + + let abs = generate_abstract(handle.cell(), &*stack); + ctx.write_layout( + DebugAbstract { + abs, + stack: (*stack).clone(), + }, + abs_path, + ) + .expect("failed to write abstract"); +} + #[derive(Clone, Copy, Debug, Default, Io)] pub struct Sky130NmosTileAutorouteIo { sd: InOut, @@ -102,11 +139,7 @@ impl AtollTile for Sky130NmosTileAutoroute { ::NestedData, ::LayoutData, )> { - let block = sky130pdk::atoll::NmosTile { - w: 1_680, - nf: 3, - len: MosLength::L150, - }; + let block = sky130pdk::atoll::NmosTile::new(1_680, MosLength::L150, 3); let mut instances = Vec::new(); From f25b0dd9e2f9b103f416b04a7baaaeea9784bf22 Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sat, 30 Dec 2023 16:24:42 -0800 Subject: [PATCH 2/8] correct nsdm/psdm/nwell dimensions, add pmos tile to sky130 atoll --- libs/atoll/src/lib.rs | 12 ++++- pdks/sky130pdk/src/atoll/mod.rs | 82 ++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/libs/atoll/src/lib.rs b/libs/atoll/src/lib.rs index d1ddecc5..fbceea91 100644 --- a/libs/atoll/src/lib.rs +++ b/libs/atoll/src/lib.rs @@ -166,7 +166,7 @@ use substrate::io::{FlatLen, Flatten, Signal}; use substrate::layout::element::Shape; use substrate::layout::tracks::{EnumeratedTracks, FiniteTracks, Tracks}; use substrate::layout::{ExportsLayoutData, Layout}; -use substrate::pdk::layers::HasPin; +use substrate::pdk::layers::{HasPin, Layers}; use substrate::pdk::Pdk; use substrate::schematic::schema::Schema; use substrate::schematic::{ @@ -189,6 +189,16 @@ impl From for LayerId { } } +/// Virtual layers for use in ATOLL. +#[derive(Layers)] +pub struct VirtualLayers { + /// The layer indicating the outline of an ATOLL tile. + /// + /// Must be aligned to the LCM grid of the cell's top layer or, + /// if the cell's top layer is layer 0, layer 1. + pub outline: Outline, +} + /// A coordinate identifying a track position in a routing volume. pub struct Coordinate { /// The lower metal layer. diff --git a/pdks/sky130pdk/src/atoll/mod.rs b/pdks/sky130pdk/src/atoll/mod.rs index 10748aca..87f2d3d0 100644 --- a/pdks/sky130pdk/src/atoll/mod.rs +++ b/pdks/sky130pdk/src/atoll/mod.rs @@ -1,7 +1,7 @@ //! SKY130 primitives for [ATOLL](atoll). use crate::layers::Sky130Layers; -use crate::mos::{MosParams, Nfet01v8}; +use crate::mos::{MosParams, Nfet01v8, Pfet01v8}; use crate::Sky130Pdk; use arcstr::ArcStr; use atoll::grid::{AbstractLayer, LayerStack, PdkLayer, RoutingGrid}; @@ -175,12 +175,17 @@ impl ExportsLayoutData for MosTile { type LayoutData = (); } -impl Layout for MosTile { +struct MosTileData { + diff: Rect, + lcm_bbox: Rect, +} + +impl MosTile { fn layout( &self, io: &mut substrate::io::layout::Builder, cell: &mut CellBuilder, - ) -> substrate::error::Result { + ) -> substrate::error::Result { let stack = cell.ctx.get_installation::>().unwrap(); let grid = RoutingGrid::new((*stack).clone(), 0..2); @@ -254,7 +259,13 @@ impl Layout for MosTile { cell.draw(Shape::new(cell.ctx.layers.licon1, cut))?; } - Ok(()) + let virtual_layers = cell.ctx.install_layers::(); + let slice = stack.slice(0..2); + let bbox = cell.bbox().unwrap(); + let lcm_bbox = slice.lcm_to_physical_rect(slice.expand_to_lcm_units(bbox)); + cell.draw(Shape::new(virtual_layers.outline, lcm_bbox))?; + + Ok(MosTileData { diff, lcm_bbox }) } } @@ -329,15 +340,13 @@ impl Layout for NmosTile { io: &mut substrate::io::layout::Builder, cell: &mut CellBuilder, ) -> substrate::error::Result { - let stack = cell.ctx.get_installation::>().unwrap(); - let slice = stack.slice(0..2); - self.tile.layout(io, cell)?; + let data = self.tile.layout(io, cell)?; + let nsdm = data.diff.expand_all(130); + let nsdm = nsdm.with_hspan(data.lcm_bbox.hspan().union(nsdm.hspan())); + cell.draw(Shape::new(cell.ctx.layers.nsdm, nsdm))?; - let bbox = cell.bbox().unwrap(); - let lcm_bbox = slice.lcm_to_physical_rect(slice.expand_to_lcm_units(bbox)); - cell.draw(Shape::new(cell.ctx.layers.nsdm, lcm_bbox))?; - cell.draw(Shape::new(cell.ctx.layers.pwell, lcm_bbox))?; - io.b.push(IoShape::with_layers(cell.ctx.layers.pwell, lcm_bbox)); + cell.draw(Shape::new(cell.ctx.layers.pwell, data.lcm_bbox))?; + io.b.push(IoShape::with_layers(cell.ctx.layers.pwell, data.lcm_bbox)); Ok(()) } @@ -353,7 +362,21 @@ impl Schematic for NmosTile { io: &substrate::io::schematic::Bundle, cell: &mut substrate::schematic::CellBuilder, ) -> substrate::error::Result { - cell.instantiate_connected(self.tile.clone(), io); + for i in 0..self.tile.nf as usize { + cell.instantiate_connected( + Nfet01v8::new(MosParams { + w: self.tile.w, + nf: 1, + l: self.tile.len.nm(), + }), + MosIoSchematic { + d: io.sd[i], + g: io.g, + s: io.sd[i + 1], + b: io.b, + }, + ) + } Ok(()) } } @@ -400,16 +423,14 @@ impl Layout for PmosTile { io: &mut substrate::io::layout::Builder, cell: &mut CellBuilder, ) -> substrate::error::Result { - let stack = cell.ctx.get_installation::>().unwrap(); - let slice = stack.slice(0..2); + let data = self.tile.layout(io, cell)?; + let psdm = data.diff.expand_all(130); + let psdm = psdm.with_hspan(data.lcm_bbox.hspan().union(psdm.hspan())); + cell.draw(Shape::new(cell.ctx.layers.psdm, psdm))?; - self.tile.layout(io, cell)?; - - let bbox = cell.bbox().unwrap(); - let lcm_bbox = slice.lcm_to_physical_rect(slice.expand_to_lcm_units(bbox)); - cell.draw(Shape::new(cell.ctx.layers.psdm, lcm_bbox))?; - cell.draw(Shape::new(cell.ctx.layers.nwell, lcm_bbox))?; - io.b.push(IoShape::with_layers(cell.ctx.layers.pwell, lcm_bbox)); + let nwell = data.diff.expand_all(180).union(data.lcm_bbox); + cell.draw(Shape::new(cell.ctx.layers.nwell, nwell))?; + io.b.push(IoShape::with_layers(cell.ctx.layers.nwell, nwell)); Ok(()) } @@ -425,6 +446,21 @@ impl Schematic for PmosTile { io: &substrate::io::schematic::Bundle, cell: &mut substrate::schematic::CellBuilder, ) -> substrate::error::Result { - self.tile.schematic(io, cell) + for i in 0..self.tile.nf as usize { + cell.instantiate_connected( + Pfet01v8::new(MosParams { + w: self.tile.w, + nf: 1, + l: self.tile.len.nm(), + }), + MosIoSchematic { + d: io.sd[i], + g: io.g, + s: io.sd[i + 1], + b: io.b, + }, + ) + } + Ok(()) } } From 78ab8c48010b34c0fb9c83e05294e4d12a48953a Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sat, 30 Dec 2023 18:07:52 -0800 Subject: [PATCH 3/8] feat(bbox): implement LayerBbox trait The LayerBbox trait allows users to find bounding boxes considering only the geometry on a specific LayerId. --- libs/geometry/src/shape.rs | 2 +- substrate/src/io/layout.rs | 11 ++++++++ substrate/src/layout/bbox.rs | 27 ++++++++++++++++++++ substrate/src/layout/element.rs | 45 +++++++++++++++++++++++++++++++++ substrate/src/layout/mod.rs | 42 ++++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 substrate/src/layout/bbox.rs diff --git a/libs/geometry/src/shape.rs b/libs/geometry/src/shape.rs index 29979b71..91f060d6 100644 --- a/libs/geometry/src/shape.rs +++ b/libs/geometry/src/shape.rs @@ -29,7 +29,7 @@ impl Shape { } } - /// If this shape is a rectangle, returns the contained polygon. + /// If this shape is a polygon, returns the contained polygon. /// Otherwise, returns [`None`]. pub fn polygon(&self) -> Option<&Polygon> { match self { diff --git a/substrate/src/io/layout.rs b/substrate/src/io/layout.rs index 97dde0ff..21cc6986 100644 --- a/substrate/src/io/layout.rs +++ b/substrate/src/io/layout.rs @@ -13,6 +13,7 @@ use geometry::transform; use geometry::transform::HasTransformedView; use geometry::union::BoundingUnion; use std::collections::HashMap; +use substrate::layout::bbox::LayerBbox; use tracing::Level; /// A layout hardware type. @@ -129,6 +130,16 @@ impl Bbox for IoShape { } } +impl LayerBbox for IoShape { + fn layer_bbox(&self, layer: LayerId) -> Option { + if self.layer.pin == layer || self.layer.drawing == layer { + self.shape.bbox() + } else { + None + } + } +} + impl IoShape { /// Creates a new [`IoShape`] from a full specification of the layers on which it should be /// drawn. diff --git a/substrate/src/layout/bbox.rs b/substrate/src/layout/bbox.rs new file mode 100644 index 00000000..26549d3f --- /dev/null +++ b/substrate/src/layout/bbox.rs @@ -0,0 +1,27 @@ +//! Bounding box utilities. + +use crate::pdk::layers::LayerId; +use geometry::prelude::*; +use geometry::union::BoundingUnion; + +/// A trait representing functions available for multi-layered objects with bounding boxes. +pub trait LayerBbox: Bbox { + /// Compute the bounding box considering only objects occupying the given layer. + fn layer_bbox(&self, layer: LayerId) -> Option; +} + +impl LayerBbox for Vec { + fn layer_bbox(&self, layer: LayerId) -> Option { + let mut bbox = None; + for item in self { + bbox = bbox.bounding_union(&item.layer_bbox(layer)); + } + bbox + } +} + +impl LayerBbox for &T { + fn layer_bbox(&self, layer: LayerId) -> Option { + (*self).layer_bbox(layer) + } +} diff --git a/substrate/src/layout/element.rs b/substrate/src/layout/element.rs index 55aee82f..e5fd46fd 100644 --- a/substrate/src/layout/element.rs +++ b/substrate/src/layout/element.rs @@ -17,6 +17,7 @@ use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use crate::io::layout::PortGeometry; +use crate::layout::bbox::LayerBbox; use crate::{ error::{Error, Result}, io::NameBuf, @@ -133,6 +134,12 @@ impl Bbox for RawCell { } } +impl LayerBbox for RawCell { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.elements.layer_bbox(layer) + } +} + impl HasTransformedView for RawCell { type TransformedView = RawCell; @@ -198,6 +205,14 @@ impl Bbox for RawInstance { } } +impl LayerBbox for RawInstance { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.cell + .layer_bbox(layer) + .map(|rect| rect.transform(self.trans)) + } +} + impl TryFrom> for RawInstance { type Error = Error; @@ -270,6 +285,16 @@ impl Bbox for Shape { } } +impl LayerBbox for Shape { + fn layer_bbox(&self, layer: LayerId) -> Option { + if self.layer == layer { + self.bbox() + } else { + None + } + } +} + impl BoundingUnion for Shape { type Output = Rect; @@ -467,6 +492,16 @@ impl Bbox for Element { } } +impl LayerBbox for Element { + fn layer_bbox(&self, layer: LayerId) -> Option { + match self { + Element::Instance(inst) => inst.layer_bbox(layer), + Element::Shape(shape) => shape.layer_bbox(layer), + Element::Text(_) => None, + } + } +} + impl From for Element { fn from(value: RawInstance) -> Self { Self::Instance(value) @@ -530,6 +565,16 @@ impl Bbox for ElementRef<'_> { } } +impl LayerBbox for ElementRef<'_> { + fn layer_bbox(&self, layer: LayerId) -> Option { + match self { + ElementRef::Instance(inst) => inst.layer_bbox(layer), + ElementRef::Shape(shape) => shape.layer_bbox(layer), + ElementRef::Text(_) => None, + } + } +} + impl<'a> From<&'a RawInstance> for ElementRef<'a> { fn from(value: &'a RawInstance) -> Self { Self::Instance(value) diff --git a/substrate/src/layout/mod.rs b/substrate/src/layout/mod.rs index a973c5fd..fc97101a 100644 --- a/substrate/src/layout/mod.rs +++ b/substrate/src/layout/mod.rs @@ -14,6 +14,7 @@ use arcstr::ArcStr; use cache::{error::TryInnerError, mem::TypeCache, CacheHandle}; pub use codegen::{Layout, LayoutData}; use examples::get_snippets; +use geometry::prelude::Rect; use geometry::{ prelude::{Bbox, Point}, transform::{ @@ -24,12 +25,15 @@ use geometry::{ use once_cell::sync::OnceCell; use crate::io::layout::{Builder, HardwareType}; +use crate::layout::bbox::LayerBbox; +use crate::pdk::layers::LayerId; use crate::pdk::Pdk; use crate::{block::Block, error::Error}; use crate::{context::PdkContext, error::Result}; use self::element::{CellId, Element, RawCell, RawInstance, Shape}; +pub mod bbox; pub mod element; pub mod error; pub mod gds; @@ -160,6 +164,12 @@ impl Bbox for Cell { } } +impl LayerBbox for Cell { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.raw.layer_bbox(layer) + } +} + /// A handle to a schematic cell that is being generated. pub struct CellHandle { pub(crate) block: Arc, @@ -242,6 +252,12 @@ impl Bbox for TransformedCell { } } +impl LayerBbox for TransformedCell { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.raw.layer_bbox(layer).transform(self.trans) + } +} + /// A generic layout instance. /// /// Stores a pointer to its underlying cell and its instantiated transformation. @@ -378,6 +394,12 @@ impl Bbox for Instance { } } +impl LayerBbox for Instance { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.cell().layer_bbox(layer) + } +} + impl TranslateMut for Instance { fn translate_mut(&mut self, p: Point) { self.transform_mut(Transformation::from_offset(p)) @@ -495,6 +517,12 @@ impl Bbox for CellBuilder { } } +impl LayerBbox for CellBuilder { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.container.layer_bbox(layer) + } +} + /// A receiver for drawing layout objects. /// /// Implements the primitive functions that layout objects need to implement [`Draw`]. @@ -618,6 +646,14 @@ impl Bbox for DrawReceiver { } } +impl LayerBbox for DrawReceiver { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.get_instances() + .layer_bbox(layer) + .bounding_union(&self.elements.layer_bbox(layer)) + } +} + /// An object that can be drawn in a [`CellBuilder`]. pub trait Draw: DrawBoxed { /// Draws `self` inside `recv`. @@ -717,6 +753,12 @@ impl Bbox for Container { } } +impl LayerBbox for Container { + fn layer_bbox(&self, layer: LayerId) -> Option { + self.recvs.layer_bbox(layer).transform(self.trans) + } +} + impl Draw for Container { fn draw(self, recv: &mut DrawReceiver) -> Result<()> { recv.draw_container(self); From 1e3d42becded1d7941872603d9e6a474c6b9492c Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sat, 30 Dec 2023 18:36:13 -0800 Subject: [PATCH 4/8] add track and grid helper functions for atoll --- libs/atoll/src/abs.rs | 2 +- libs/atoll/src/grid.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libs/atoll/src/abs.rs b/libs/atoll/src/abs.rs index adb5bef2..1172a0d6 100644 --- a/libs/atoll/src/abs.rs +++ b/libs/atoll/src/abs.rs @@ -41,7 +41,7 @@ pub struct GridCoord { /// /// There are three coordinate systems used within the abstract view. /// * Physical coordinates: the raw physical coordinate system of the cell, expressed in PDK database units. -/// * Track coordinates: track indices indexing the ATOLL [`LayerStack`]. Track is 0 is typically centered at the origin, or immediately to the upper left of the origin. +/// * Track coordinates: track indices indexing the ATOLL [`LayerStack`]. Track 0 is typically centered at the origin, or immediately to the upper left of the origin. /// * Grid coordinates: these have the same spacing as track coordinates, but are shifted so that (0, 0) is always at the lower left. These are used to index [`LayerAbstract`]s. /// /// ATOLL provides the following utilities for converting between these coordinate systems: diff --git a/libs/atoll/src/grid.rs b/libs/atoll/src/grid.rs index d2b51698..4fbba289 100644 --- a/libs/atoll/src/grid.rs +++ b/libs/atoll/src/grid.rs @@ -365,6 +365,26 @@ impl RoutingGrid { tracks.track(track) } + /// The tracks on the given layer. + pub fn tracks(&self, layer: usize) -> UniformTracks { + self.stack.tracks(layer) + } + + /// Returns the track grid for the given layer. + /// + /// Returns a tuple containing the vertical going tracks followed by the horizontal going tracks. + /// In other words, the first element of the tuple is indexed by an x-coordinate, + /// and the second element of the tuple is indexed by a y-coordinate. + pub fn track_grid(&self, layer: usize) -> (UniformTracks, UniformTracks) { + let tracks = self.tracks(layer); + let adj_tracks = self.stack.tracks(self.grid_defining_layer(layer)); + + match self.stack.layer(layer).dir().track_dir() { + Dir::Horiz => (adj_tracks, tracks), + Dir::Vert => (tracks, adj_tracks), + } + } + /// Calculates the bounds of a particular track on the given layer. /// /// The start and end coordinates are with respect to tracks on the grid defining layer. From b0231d7613dce3b1f858fcfca5f9a37a88b16a79 Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sat, 30 Dec 2023 18:46:29 -0800 Subject: [PATCH 5/8] feat(atoll): use virtual layer for tile outline feat(atoll): make abstract generator function part of Abstract --- libs/atoll/src/abs.rs | 151 ++++++++++++++++++++++------------------- libs/atoll/src/lib.rs | 6 +- tests/src/atoll/mod.rs | 8 +-- 3 files changed, 87 insertions(+), 78 deletions(-) diff --git a/libs/atoll/src/abs.rs b/libs/atoll/src/abs.rs index 1172a0d6..a980ba5c 100644 --- a/libs/atoll/src/abs.rs +++ b/libs/atoll/src/abs.rs @@ -11,13 +11,15 @@ use substrate::geometry::bbox::Bbox; use substrate::geometry::transform::Transformation; use substrate::layout::element::Text; +use substrate::context::PdkContext; use substrate::geometry::point::Point; use substrate::geometry::rect::Rect; use substrate::io::layout::Builder; +use substrate::layout::bbox::LayerBbox; use substrate::layout::element::Shape; use substrate::layout::element::{CellId, Element, RawCell}; use substrate::layout::{CellBuilder, Draw, DrawReceiver, ExportsLayoutData, Layout}; -use substrate::pdk::layers::HasPin; +use substrate::pdk::layers::{HasPin, Layer}; use substrate::pdk::Pdk; use substrate::schematic::ExportsNestedData; use substrate::{arcstr, layout}; @@ -148,6 +150,83 @@ impl AtollAbstract { pub fn physical_origin(&self) -> Point { self.lcm_bounds.lower_left() * self.slice().lcm_units() } + + /// Generates an abstract view of a layout cell. + pub fn generate( + ctx: &PdkContext, + layout: &layout::Cell, + ) -> AtollAbstract { + let stack = ctx + .get_installation::>() + .expect("must install ATOLL layer stack"); + let virtual_layers = ctx.install_layers::(); + + let cell = layout.raw(); + let bbox = cell + .layer_bbox(virtual_layers.outline.id()) + .expect("cell must provide an outline on ATOLL virtual layer"); + + let top = top_layer(cell, &*stack) + .expect("cell did not have any ATOLL routing layers; cannot produce an abstract"); + let top = if top == 0 { 1 } else { top }; + + let slice = stack.slice(0..top + 1); + + let xmin = div_floor(bbox.left(), slice.lcm_unit_width()); + let xmax = div_ceil(bbox.right(), slice.lcm_unit_width()); + let ymin = div_floor(bbox.bot(), slice.lcm_unit_height()); + let ymax = div_ceil(bbox.top(), slice.lcm_unit_height()); + let lcm_bounds = Rect::from_sides(xmin, ymin, xmax, ymax); + + let nx = lcm_bounds.width(); + let ny = lcm_bounds.height(); + + let grid = RoutingGrid::new((*stack).clone(), 0..top + 1); + let mut state = RoutingState::new((*stack).clone(), top, nx, ny); + let mut ports = Vec::new(); + for (i, (name, geom)) in cell.ports().enumerate() { + let net = NetId(i); + ports.push(net); + if let Some(layer) = stack.layer_idx(geom.primary.layer().drawing()) { + let rect = match geom.primary.shape() { + substrate::geometry::shape::Shape::Rect(r) => *r, + substrate::geometry::shape::Shape::Polygon(p) => { + p.bbox().expect("empty polygons are unsupported") + } + }; + println!("source rect = {rect:?}"); + if let Some(rect) = grid.shrink_to_grid(rect, layer) { + println!("grid rect = {rect:?}"); + for x in rect.left()..=rect.right() { + for y in rect.bot()..=rect.top() { + let xofs = xmin * slice.lcm_unit_width() / grid.xpitch(layer); + let yofs = ymin * slice.lcm_unit_height() / grid.ypitch(layer); + state.layer_mut(layer)[((x - xofs) as usize, (y - yofs) as usize)] = + PointState::Routed { + net, + via_down: false, + via_up: false, + }; + } + } + } + } + } + + let layers = state + .layers + .into_iter() + .map(|states| LayerAbstract::Detailed { states }) + .collect(); + + AtollAbstract { + top_layer: top, + lcm_bounds, + grid: RoutingGrid::new((*stack).clone(), 0..top + 1), + ports, + layers, + } + } } /// The abstracted state of a single routing layer. @@ -197,76 +276,6 @@ fn top_layer_inner( top } -/// Generates an abstract view of a layout cell. -pub fn generate_abstract( - layout: &layout::Cell, - stack: &LayerStack, -) -> AtollAbstract { - let cell = layout.raw(); - let bbox = cell.bbox().unwrap(); - - let top = top_layer(cell, stack) - .expect("cell did not have any ATOLL routing layers; cannot produce an abstract"); - let top = if top == 0 { 1 } else { top }; - - let slice = stack.slice(0..top + 1); - - let xmin = div_floor(bbox.left(), slice.lcm_unit_width()); - let xmax = div_ceil(bbox.right(), slice.lcm_unit_width()); - let ymin = div_floor(bbox.bot(), slice.lcm_unit_height()); - let ymax = div_ceil(bbox.top(), slice.lcm_unit_height()); - let lcm_bounds = Rect::from_sides(xmin, ymin, xmax, ymax); - - let nx = lcm_bounds.width(); - let ny = lcm_bounds.height(); - - let grid = RoutingGrid::new(stack.clone(), 0..top + 1); - let mut state = RoutingState::new(stack.clone(), top, nx, ny); - let mut ports = Vec::new(); - for (i, (name, geom)) in cell.ports().enumerate() { - let net = NetId(i); - ports.push(net); - if let Some(layer) = stack.layer_idx(geom.primary.layer().drawing()) { - let rect = match geom.primary.shape() { - substrate::geometry::shape::Shape::Rect(r) => *r, - substrate::geometry::shape::Shape::Polygon(p) => { - p.bbox().expect("empty polygons are unsupported") - } - }; - println!("source rect = {rect:?}"); - if let Some(rect) = grid.shrink_to_grid(rect, layer) { - println!("grid rect = {rect:?}"); - for x in rect.left()..=rect.right() { - for y in rect.bot()..=rect.top() { - let xofs = xmin * slice.lcm_unit_width() / grid.xpitch(layer); - let yofs = ymin * slice.lcm_unit_height() / grid.ypitch(layer); - state.layer_mut(layer)[((x - xofs) as usize, (y - yofs) as usize)] = - PointState::Routed { - net, - via_down: false, - via_up: false, - }; - } - } - } - } - } - - let layers = state - .layers - .into_iter() - .map(|states| LayerAbstract::Detailed { states }) - .collect(); - - AtollAbstract { - top_layer: top, - lcm_bounds, - grid: RoutingGrid::new(stack.clone(), 0..top + 1), - ports, - layers, - } -} - #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct DebugAbstract { pub abs: AtollAbstract, diff --git a/libs/atoll/src/lib.rs b/libs/atoll/src/lib.rs index fbceea91..106f776e 100644 --- a/libs/atoll/src/lib.rs +++ b/libs/atoll/src/lib.rs @@ -145,7 +145,7 @@ pub mod abs; pub mod grid; -use crate::abs::{generate_abstract, AtollAbstract}; +use crate::abs::AtollAbstract; use crate::grid::{LayerStack, PdkLayer}; use ::grid::Grid; use ena::unify::{UnifyKey, UnifyValue}; @@ -503,7 +503,7 @@ impl<'a, PDK: Pdk + Schema> AtollTileBuilder<'a, PDK> { ) -> Instance { let layout = self.layout.generate(block.clone()); let schematic = self.schematic.instantiate(block); - let abs = generate_abstract(layout.raw_cell(), self.layer_stack.as_ref()); + let abs = AtollAbstract::generate(&self.layout.ctx, layout.raw_cell()); let top = abs.top_layer; Instance { layout, @@ -522,7 +522,7 @@ impl<'a, PDK: Pdk + Schema> AtollTileBuilder<'a, PDK> { let layout = self.layout.generate(wrapper.clone()); let schematic = self.schematic.instantiate(wrapper); // todo: generate abstract from AtollTile trait directly - let abs = generate_abstract(layout.raw_cell(), self.layer_stack.as_ref()); + let abs = AtollAbstract::generate(&self.layout.ctx, layout.raw_cell()); let top = abs.top_layer; Instance { layout, diff --git a/tests/src/atoll/mod.rs b/tests/src/atoll/mod.rs index 8c7b4bfb..2edb7d8a 100644 --- a/tests/src/atoll/mod.rs +++ b/tests/src/atoll/mod.rs @@ -1,6 +1,6 @@ use crate::paths::get_path; use crate::shared::pdk::sky130_open_ctx; -use atoll::abs::{generate_abstract, DebugAbstract}; +use atoll::abs::{DebugAbstract, AtollAbstract}; use atoll::grid::{LayerStack, PdkLayer}; use atoll::{AtollIo, AtollTile, AtollTileBuilder, AtollTileWrapper}; use geometry::point::Point; @@ -59,7 +59,7 @@ fn sky130_atoll_nmos_tile() { // todo: add mechanism to have multiple ATOLL layer stacks (one per PDK) let stack = ctx.get_installation::>().unwrap(); - let abs = generate_abstract(handle.cell(), &*stack); + let abs = AtollAbstract::generate(&ctx, handle.cell()); ctx.write_layout( DebugAbstract { abs, @@ -100,7 +100,7 @@ fn sky130_atoll_pmos_tile() { let stack = ctx.get_installation::>().unwrap(); - let abs = generate_abstract(handle.cell(), &*stack); + let abs = AtollAbstract::generate(&ctx, handle.cell()); ctx.write_layout( DebugAbstract { abs, @@ -195,7 +195,7 @@ fn sky130_atoll_nmos_tile_autoroute() { let handle = ctx.generate_layout(block); let stack = ctx.get_installation::>().unwrap(); - let abs = generate_abstract(handle.cell(), &stack); + let abs = AtollAbstract::generate(&ctx, handle.cell()); ctx.write_layout( DebugAbstract { abs, From 60230a0c4d8d7d68dcfa44ee4e050fe62fc34ba9 Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sat, 30 Dec 2023 18:47:11 -0800 Subject: [PATCH 6/8] format --- tests/src/atoll/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/atoll/mod.rs b/tests/src/atoll/mod.rs index 2edb7d8a..11354cde 100644 --- a/tests/src/atoll/mod.rs +++ b/tests/src/atoll/mod.rs @@ -1,6 +1,6 @@ use crate::paths::get_path; use crate::shared::pdk::sky130_open_ctx; -use atoll::abs::{DebugAbstract, AtollAbstract}; +use atoll::abs::{AtollAbstract, DebugAbstract}; use atoll::grid::{LayerStack, PdkLayer}; use atoll::{AtollIo, AtollTile, AtollTileBuilder, AtollTileWrapper}; use geometry::point::Point; From 943de4d3011d80ac1578b548991124a4843df042 Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sat, 30 Dec 2023 18:56:34 -0800 Subject: [PATCH 7/8] fix lints --- libs/atoll/src/abs.rs | 4 ++-- libs/atoll/src/grid.rs | 5 ++--- libs/atoll/src/lib.rs | 33 +++++++++++++++------------------ libs/geometry/src/transform.rs | 2 -- substrate/src/block.rs | 2 +- tests/src/atoll/mod.rs | 8 ++++---- 6 files changed, 24 insertions(+), 30 deletions(-) diff --git a/libs/atoll/src/abs.rs b/libs/atoll/src/abs.rs index a980ba5c..ef888378 100644 --- a/libs/atoll/src/abs.rs +++ b/libs/atoll/src/abs.rs @@ -166,7 +166,7 @@ impl AtollAbstract { .layer_bbox(virtual_layers.outline.id()) .expect("cell must provide an outline on ATOLL virtual layer"); - let top = top_layer(cell, &*stack) + let top = top_layer(cell, &stack) .expect("cell did not have any ATOLL routing layers; cannot produce an abstract"); let top = if top == 0 { 1 } else { top }; @@ -184,7 +184,7 @@ impl AtollAbstract { let grid = RoutingGrid::new((*stack).clone(), 0..top + 1); let mut state = RoutingState::new((*stack).clone(), top, nx, ny); let mut ports = Vec::new(); - for (i, (name, geom)) in cell.ports().enumerate() { + for (i, (_name, geom)) in cell.ports().enumerate() { let net = NetId(i); ports.push(net); if let Some(layer) = stack.layer_idx(geom.primary.layer().drawing()) { diff --git a/libs/atoll/src/grid.rs b/libs/atoll/src/grid.rs index 4fbba289..c63b3598 100644 --- a/libs/atoll/src/grid.rs +++ b/libs/atoll/src/grid.rs @@ -4,9 +4,8 @@ use grid::Grid; use num::integer::{div_ceil, div_floor}; use serde::{Deserialize, Serialize}; use std::any::Any; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut, Range}; + +use std::ops::Range; use substrate::context::{ContextBuilder, Installation}; use substrate::geometry::corner::Corner; use substrate::geometry::dims::Dims; diff --git a/libs/atoll/src/lib.rs b/libs/atoll/src/lib.rs index 106f776e..10eb844e 100644 --- a/libs/atoll/src/lib.rs +++ b/libs/atoll/src/lib.rs @@ -147,31 +147,28 @@ pub mod grid; use crate::abs::AtollAbstract; use crate::grid::{LayerStack, PdkLayer}; -use ::grid::Grid; -use ena::unify::{UnifyKey, UnifyValue}; + +use ena::unify::UnifyKey; use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::marker::PhantomData; -use std::ops::Deref; +use std::collections::HashMap; + use std::sync::Arc; use substrate::arcstr::ArcStr; use substrate::block::Block; -use substrate::context::{prepare_cell_builder, Context, PdkContext}; +use substrate::context::{prepare_cell_builder, PdkContext}; use substrate::geometry::polygon::Polygon; -use substrate::geometry::prelude::{Bbox, Dir, Point, Transformation}; -use substrate::geometry::transform::{HasTransformedView, Translate, TranslateMut}; -use substrate::io::layout::{Builder, PortGeometry, PortGeometryBuilder}; -use substrate::io::schematic::{Bundle, Connect, Node, Terminal, TerminalView}; -use substrate::io::{FlatLen, Flatten, Signal}; +use substrate::geometry::prelude::{Bbox, Dir, Point}; +use substrate::geometry::transform::{Translate, TranslateMut}; +use substrate::io::layout::{Builder, PortGeometry}; +use substrate::io::schematic::{Bundle, Connect, Node, TerminalView}; +use substrate::io::{FlatLen, Flatten}; use substrate::layout::element::Shape; -use substrate::layout::tracks::{EnumeratedTracks, FiniteTracks, Tracks}; + use substrate::layout::{ExportsLayoutData, Layout}; use substrate::pdk::layers::{HasPin, Layers}; use substrate::pdk::Pdk; use substrate::schematic::schema::Schema; -use substrate::schematic::{ - CellId, ExportsNestedData, HasNestedView, InstanceId, InstancePath, SchemaCellHandle, Schematic, -}; +use substrate::schematic::{CellId, ExportsNestedData, Schematic}; use substrate::{io, layout, schematic}; /// Identifies nets in a routing solver. @@ -504,7 +501,7 @@ impl<'a, PDK: Pdk + Schema> AtollTileBuilder<'a, PDK> { let layout = self.layout.generate(block.clone()); let schematic = self.schematic.instantiate(block); let abs = AtollAbstract::generate(&self.layout.ctx, layout.raw_cell()); - let top = abs.top_layer; + let _top = abs.top_layer; Instance { layout, schematic, @@ -523,7 +520,7 @@ impl<'a, PDK: Pdk + Schema> AtollTileBuilder<'a, PDK> { let schematic = self.schematic.instantiate(wrapper); // todo: generate abstract from AtollTile trait directly let abs = AtollAbstract::generate(&self.layout.ctx, layout.raw_cell()); - let top = abs.top_layer; + let _top = abs.top_layer; Instance { layout, schematic, @@ -582,7 +579,7 @@ impl<'a, PDK: Pdk + Schema> AtollTileBuilder<'a, PDK> { } /// Match the given nodes and port geometry. - pub fn match_geometry(&mut self, s1: D1, s2: D2) + pub fn match_geometry(&mut self, _s1: D1, _s2: D2) where D1: Flatten, D2: Flatten, diff --git a/libs/geometry/src/transform.rs b/libs/geometry/src/transform.rs index b5b55eb5..3951004c 100644 --- a/libs/geometry/src/transform.rs +++ b/libs/geometry/src/transform.rs @@ -403,8 +403,6 @@ impl HasTransformedView for Vec { #[cfg(test)] mod tests { - use std::collections::HashMap; - use std::sync::{Arc, Mutex}; use approx::assert_relative_eq; diff --git a/substrate/src/block.rs b/substrate/src/block.rs index 09ec2546..18c9ef42 100644 --- a/substrate/src/block.rs +++ b/substrate/src/block.rs @@ -47,6 +47,6 @@ impl Block for Arc { } fn io(&self) -> Self::Io { - T::io(&self) + T::io(self) } } diff --git a/tests/src/atoll/mod.rs b/tests/src/atoll/mod.rs index 11354cde..b37df9e6 100644 --- a/tests/src/atoll/mod.rs +++ b/tests/src/atoll/mod.rs @@ -4,16 +4,16 @@ use atoll::abs::{AtollAbstract, DebugAbstract}; use atoll::grid::{LayerStack, PdkLayer}; use atoll::{AtollIo, AtollTile, AtollTileBuilder, AtollTileWrapper}; use geometry::point::Point; -use geometry::rect::Rect; + use serde::{Deserialize, Serialize}; use sky130pdk::atoll::{MosLength, NmosTile}; use sky130pdk::{Sky130CommercialSchema, Sky130Pdk}; use spice::netlist::NetlistOptions; use spice::Spice; use substrate::block::Block; -use substrate::io::layout::{HardwareType, IoShape}; +use substrate::io::layout::HardwareType; use substrate::io::{InOut, Io, Signal}; -use substrate::layout::tiling::{GridTiler, Tile}; + use substrate::layout::{CellBuilder, ExportsLayoutData, Layout}; use substrate::schematic; use substrate::schematic::netlist::ConvertibleNetlister; @@ -144,7 +144,7 @@ impl AtollTile for Sky130NmosTileAutoroute { let mut instances = Vec::new(); for i in 0..3 { - let mut inst = cell.generate_primitive(block.clone()); + let mut inst = cell.generate_primitive(block); inst.translate_mut(Point::new(5 * i, 0)); cell.draw(&inst)?; From 4019d4d27e95c7bcf9f08f68676c52a6a0180c0e Mon Sep 17 00:00:00 2001 From: Rahul Kumar Date: Sun, 31 Dec 2023 12:44:02 -0800 Subject: [PATCH 8/8] fix(atoll): extend gate pin of mos tile to grid --- pdks/sky130pdk/src/atoll/mod.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pdks/sky130pdk/src/atoll/mod.rs b/pdks/sky130pdk/src/atoll/mod.rs index 87f2d3d0..ce5c8c22 100644 --- a/pdks/sky130pdk/src/atoll/mod.rs +++ b/pdks/sky130pdk/src/atoll/mod.rs @@ -16,6 +16,7 @@ use substrate::geometry::span::Span; use substrate::io::layout::IoShape; use substrate::io::{Array, InOut, Input, Io, MosIoSchematic, Signal}; use substrate::layout::element::Shape; +use substrate::layout::tracks::{RoundingMode, Tracks}; use substrate::layout::{CellBuilder, ExportsLayoutData, Layout}; use substrate::pdk::layers::Layer; use substrate::schematic::{ExportsNestedData, Schematic}; @@ -240,22 +241,30 @@ impl MosTile { ); cell.draw(Shape::new(cell.ctx.layers.poly, poly))?; + let trk = grid.tracks(1).to_track_idx(poly.bot(), RoundingMode::Down); + let bot = grid.tracks(1).track(trk).center() - 100; let poly_li = Rect::from_sides( tracks[1].left(), - poly.bot(), + bot, tracks[tracks.len() - 2].right(), poly.top(), ); cell.draw(Shape::new(cell.ctx.layers.li1, poly_li))?; io.g.push(IoShape::with_layers(cell.ctx.layers.li1, poly_li)); - let npc = poly_li - .expand_dir(Dir::Vert, 10) - .expand_dir(Dir::Horiz, 100); + let npc = Rect::from_spans( + poly_li.hspan(), + Span::new(poly_li.top(), poly_li.top() - 350), + ) + .expand_dir(Dir::Vert, 10) + .expand_dir(Dir::Horiz, 100); cell.draw(Shape::new(cell.ctx.layers.npc, npc))?; #[allow(clippy::needless_range_loop)] for i in 1..self.nf as usize { - let cut = Rect::from_spans(tracks[i].hspan(), poly_li.shrink_all(90).unwrap().vspan()); + let cut = Rect::from_spans( + tracks[i].hspan(), + Span::new(poly_li.top() - 90, poly_li.top() - 260), + ); cell.draw(Shape::new(cell.ctx.layers.licon1, cut))?; }