| 
 | 1 | +use atoll::fold::Foldable;  | 
 | 2 | +use atoll::straps::{GreedyStrapper, LayerStrappingParams, StrappingParams};  | 
 | 3 | +use atoll::{Tile, TileData, route::GreedyRouter};  | 
 | 4 | +use sky130::{  | 
 | 5 | +    Sky130,  | 
 | 6 | +    atoll::{NmosTile, PtapTile, Sky130ViaMaker},  | 
 | 7 | +    res::PrecisionResistorCell,  | 
 | 8 | +};  | 
 | 9 | +use substrate::types::codegen::{PortGeometryBundle, View};  | 
 | 10 | +use substrate::{  | 
 | 11 | +    block::Block,  | 
 | 12 | +    geometry::align::AlignMode,  | 
 | 13 | +    geometry::bbox::Bbox,  | 
 | 14 | +    types::{FlatLen, InOut, Input, Io, Signal, layout::PortGeometryBuilder},  | 
 | 15 | +};  | 
 | 16 | + | 
 | 17 | +#[derive(Debug, Default, Clone, Io)]  | 
 | 18 | +pub struct ResistorBankSliceIo {  | 
 | 19 | +    pub din: Input<Signal>,  | 
 | 20 | +    pub en: Input<Signal>,  | 
 | 21 | +    pub vss: InOut<Signal>,  | 
 | 22 | +}  | 
 | 23 | + | 
 | 24 | +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Block)]  | 
 | 25 | +#[substrate(io = "ResistorBankSliceIo")]  | 
 | 26 | +pub struct ResistorBankSlice {  | 
 | 27 | +    pub n: NmosTile,  | 
 | 28 | +    pub res: PrecisionResistorCell,  | 
 | 29 | +    pub tap: PtapTile,  | 
 | 30 | +}  | 
 | 31 | + | 
 | 32 | +impl Foldable for ResistorBankSlice {  | 
 | 33 | +    type ViaMaker = Sky130ViaMaker;  | 
 | 34 | +    fn via_maker() -> Self::ViaMaker {  | 
 | 35 | +        Sky130ViaMaker  | 
 | 36 | +    }  | 
 | 37 | +}  | 
 | 38 | + | 
 | 39 | +impl Tile for ResistorBankSlice {  | 
 | 40 | +    type Schema = Sky130;  | 
 | 41 | +    type NestedData = ();  | 
 | 42 | +    type LayoutBundle = View<ResistorBankSliceIo, PortGeometryBundle<Self::Schema>>;  | 
 | 43 | +    type LayoutData = ();  | 
 | 44 | + | 
 | 45 | +    fn tile<'a>(  | 
 | 46 | +        &self,  | 
 | 47 | +        io: &'a substrate::types::schematic::IoNodeBundle<Self>,  | 
 | 48 | +        cell: &mut atoll::TileBuilder<'a, Self::Schema>,  | 
 | 49 | +    ) -> substrate::error::Result<atoll::TileData<Self>> {  | 
 | 50 | +        let mut din = PortGeometryBuilder::new();  | 
 | 51 | +        let mut en = PortGeometryBuilder::new();  | 
 | 52 | +        let mut vss = PortGeometryBuilder::new();  | 
 | 53 | +        let x = cell.signal("x", Signal);  | 
 | 54 | +        let n = cell.generate_primitive_named(self.n, "nmos");  | 
 | 55 | +        for i in 0..n.io().g.len() {  | 
 | 56 | +            cell.connect(n.io().g[i], io.en);  | 
 | 57 | +        }  | 
 | 58 | +        for i in 0..n.io().sd.len() {  | 
 | 59 | +            if i % 2 == 0 {  | 
 | 60 | +                cell.connect(n.io().sd[i], io.din);  | 
 | 61 | +            } else {  | 
 | 62 | +                cell.connect(n.io().sd[i], x);  | 
 | 63 | +            }  | 
 | 64 | +        }  | 
 | 65 | +        cell.connect(n.io().b, io.vss);  | 
 | 66 | +        let mut tap = cell.generate_primitive_named(self.tap, "tap");  | 
 | 67 | +        cell.connect(tap.io().vnb, io.vss);  | 
 | 68 | +        let mut res = cell.generate_primitive_named(self.res, "res");  | 
 | 69 | +        cell.connect(res.io().p, x);  | 
 | 70 | +        cell.connect(res.io().n, io.vss);  | 
 | 71 | +        let nbbox = n.lcm_bounds();  | 
 | 72 | +        res.align_rect_mut(nbbox, AlignMode::CenterHorizontal, 0);  | 
 | 73 | +        res.align_rect_mut(nbbox, AlignMode::Beneath, 0);  | 
 | 74 | +        let resbbox = res.lcm_bounds();  | 
 | 75 | +        tap.align_rect_mut(nbbox, AlignMode::Left, 0);  | 
 | 76 | +        tap.align_rect_mut(resbbox, AlignMode::Beneath, 0);  | 
 | 77 | + | 
 | 78 | +        let n = cell.draw(n)?;  | 
 | 79 | +        let res = cell.draw(res)?;  | 
 | 80 | +        let tap = cell.draw(tap)?;  | 
 | 81 | + | 
 | 82 | +        en.merge(n.layout.io().g[0].clone());  | 
 | 83 | +        din.merge(n.layout.io().sd[1].clone());  | 
 | 84 | +        vss.merge(n.layout.io().b);  | 
 | 85 | +        vss.merge(res.layout.io().n);  | 
 | 86 | +        vss.merge(tap.layout.io().vnb);  | 
 | 87 | + | 
 | 88 | +        cell.set_top_layer(2);  | 
 | 89 | +        cell.set_router(GreedyRouter::new());  | 
 | 90 | +        cell.set_via_maker(Sky130ViaMaker);  | 
 | 91 | +        cell.set_strapper(GreedyStrapper);  | 
 | 92 | +        cell.set_strapping(  | 
 | 93 | +            io.vss,  | 
 | 94 | +            StrappingParams::new(1, vec![LayerStrappingParams::ViaDown { min_period: 30 }]),  | 
 | 95 | +        );  | 
 | 96 | + | 
 | 97 | +        Ok(TileData {  | 
 | 98 | +            nested_data: (),  | 
 | 99 | +            layout_bundle: ResistorBankSliceIoView {  | 
 | 100 | +                din: din.build()?,  | 
 | 101 | +                en: en.build()?,  | 
 | 102 | +                vss: vss.build()?,  | 
 | 103 | +            },  | 
 | 104 | +            layout_data: (),  | 
 | 105 | +            outline: cell.layout.bbox_rect(),  | 
 | 106 | +        })  | 
 | 107 | +    }  | 
 | 108 | +}  | 
 | 109 | + | 
 | 110 | +#[cfg(test)]  | 
 | 111 | +mod tests {  | 
 | 112 | +    use super::*;  | 
 | 113 | + | 
 | 114 | +    use atoll::TileWrapper;  | 
 | 115 | +    use atoll::fold::{FoldedArray, PinConfig};  | 
 | 116 | +    use scir::netlist::ConvertibleNetlister;  | 
 | 117 | +    use sky130::Sky130SrcNdaSchema;  | 
 | 118 | +    use sky130::atoll::MosLength;  | 
 | 119 | +    use sky130::res::{PrecisionResistor, PrecisionResistorWidth};  | 
 | 120 | +    use sky130::{Sky130, layout::to_gds};  | 
 | 121 | +    use spice::{Spice, netlist::NetlistOptions};  | 
 | 122 | +    use std::path::PathBuf;  | 
 | 123 | +    use substrate::context::Context;  | 
 | 124 | +    use substrate::geometry::dir::Dir;  | 
 | 125 | +    use substrate::geometry::side::Side;  | 
 | 126 | + | 
 | 127 | +    pub fn sky130_src_nda_ctx() -> Context {  | 
 | 128 | +        // Open PDK needed for standard cells.  | 
 | 129 | +        let open_pdk_root = std::env::var("SKY130_OPEN_PDK_ROOT")  | 
 | 130 | +            .expect("the SKY130_OPEN_PDK_ROOT environment variable must be set");  | 
 | 131 | +        let src_nda_pdk_root = std::env::var("SKY130_SRC_NDA_PDK_ROOT")  | 
 | 132 | +            .expect("the SKY130_SRC_NDA_PDK_ROOT environment variable must be set");  | 
 | 133 | +        Context::builder()  | 
 | 134 | +            .install(Sky130::src_nda(open_pdk_root, src_nda_pdk_root))  | 
 | 135 | +            .build()  | 
 | 136 | +    }  | 
 | 137 | + | 
 | 138 | +    #[test]  | 
 | 139 | +    fn resistor_bank_slice_lvs() {  | 
 | 140 | +        let work_dir = PathBuf::from(concat!(  | 
 | 141 | +            env!("CARGO_MANIFEST_DIR"),  | 
 | 142 | +            "/build/resistor_bank_slice_lvs"  | 
 | 143 | +        ));  | 
 | 144 | +        let gds_path = work_dir.join("layout.gds");  | 
 | 145 | +        let netlist_path = work_dir.join("netlist.sp");  | 
 | 146 | +        let ctx = sky130_src_nda_ctx();  | 
 | 147 | + | 
 | 148 | +        let block = TileWrapper::new(ResistorBankSlice {  | 
 | 149 | +            tap: PtapTile::new(7, 4),  | 
 | 150 | +            res: PrecisionResistorCell {  | 
 | 151 | +                resistor: PrecisionResistor {  | 
 | 152 | +                    width: PrecisionResistorWidth::W285,  | 
 | 153 | +                    length: 4_000,  | 
 | 154 | +                },  | 
 | 155 | +                dir: Dir::Vert,  | 
 | 156 | +            },  | 
 | 157 | +            n: NmosTile::new(2_000, MosLength::L150, 6),  | 
 | 158 | +        });  | 
 | 159 | + | 
 | 160 | +        let scir = ctx  | 
 | 161 | +            .export_scir(block)  | 
 | 162 | +            .unwrap()  | 
 | 163 | +            .scir  | 
 | 164 | +            .convert_schema::<Sky130SrcNdaSchema>()  | 
 | 165 | +            .unwrap()  | 
 | 166 | +            .convert_schema::<Spice>()  | 
 | 167 | +            .unwrap()  | 
 | 168 | +            .build()  | 
 | 169 | +            .unwrap();  | 
 | 170 | +        Spice  | 
 | 171 | +            .write_scir_netlist_to_file(&scir, &netlist_path, NetlistOptions::default())  | 
 | 172 | +            .expect("failed to write netlist");  | 
 | 173 | + | 
 | 174 | +        ctx.write_layout(block, to_gds, &gds_path)  | 
 | 175 | +            .expect("failed to write layout");  | 
 | 176 | +    }  | 
 | 177 | + | 
 | 178 | +    #[test]  | 
 | 179 | +    fn resistor_bank() {  | 
 | 180 | +        let work_dir = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/build/resistor_bank"));  | 
 | 181 | +        let gds_path = work_dir.join("layout.gds");  | 
 | 182 | +        let netlist_path = work_dir.join("netlist.sp");  | 
 | 183 | +        let ctx = sky130_src_nda_ctx();  | 
 | 184 | + | 
 | 185 | +        let block = TileWrapper::new(FoldedArray {  | 
 | 186 | +            rows: 2,  | 
 | 187 | +            cols: 20,  | 
 | 188 | +            pins: vec![  | 
 | 189 | +                PinConfig::Parallel { layer: 1 },  | 
 | 190 | +                PinConfig::Escape {  | 
 | 191 | +                    layer: 0,  | 
 | 192 | +                    side: Side::Top,  | 
 | 193 | +                },  | 
 | 194 | +                PinConfig::Parallel { layer: 1 },  | 
 | 195 | +            ],  | 
 | 196 | +            tile: ResistorBankSlice {  | 
 | 197 | +                tap: PtapTile::new(7, 4),  | 
 | 198 | +                res: PrecisionResistorCell {  | 
 | 199 | +                    resistor: PrecisionResistor {  | 
 | 200 | +                        width: PrecisionResistorWidth::W141,  | 
 | 201 | +                        length: 2_000,  | 
 | 202 | +                    },  | 
 | 203 | +                    dir: Dir::Vert,  | 
 | 204 | +                },  | 
 | 205 | +                n: NmosTile::new(2_000, MosLength::L150, 6),  | 
 | 206 | +            },  | 
 | 207 | +            top_layer: 3,  | 
 | 208 | +            dir: Dir::Horiz,  | 
 | 209 | +        });  | 
 | 210 | + | 
 | 211 | +        let scir = ctx  | 
 | 212 | +            .export_scir(block.clone())  | 
 | 213 | +            .unwrap()  | 
 | 214 | +            .scir  | 
 | 215 | +            .convert_schema::<Sky130SrcNdaSchema>()  | 
 | 216 | +            .unwrap()  | 
 | 217 | +            .convert_schema::<Spice>()  | 
 | 218 | +            .unwrap()  | 
 | 219 | +            .build()  | 
 | 220 | +            .unwrap();  | 
 | 221 | +        Spice  | 
 | 222 | +            .write_scir_netlist_to_file(&scir, &netlist_path, NetlistOptions::default())  | 
 | 223 | +            .expect("failed to write netlist");  | 
 | 224 | +        ctx.write_layout(block, to_gds, &gds_path)  | 
 | 225 | +            .expect("failed to write layout");  | 
 | 226 | +    }  | 
 | 227 | +}  | 
0 commit comments