diff --git a/src/blocks/bitcell_array/mod.rs b/src/blocks/bitcell_array/mod.rs index f4c0b7d1..ecf8aac8 100644 --- a/src/blocks/bitcell_array/mod.rs +++ b/src/blocks/bitcell_array/mod.rs @@ -466,6 +466,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vdd, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), @@ -501,6 +502,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path), + vmeas_conn: AcImpedanceTbNode::Vss, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), diff --git a/src/blocks/decoder/schematic.rs b/src/blocks/decoder/schematic.rs index 59cb6cab..2b9b5ff4 100644 --- a/src/blocks/decoder/schematic.rs +++ b/src/blocks/decoder/schematic.rs @@ -23,7 +23,7 @@ impl Decoder { let decode = ctx.bus_port("decode", out_bits, Direction::Output); let decode_b = ctx.bus_port("decode_b", out_bits, Direction::Output); - let port_names = vec!["a", "b", "c"]; + let port_names = ["a", "b", "c"]; // Initialize all gates in the decoder tree using BFS. let mut queue = VecDeque::<(Option, &TreeNode)>::new(); diff --git a/src/blocks/gate/mod.rs b/src/blocks/gate/mod.rs index 7cd054ac..53bc0672 100644 --- a/src/blocks/gate/mod.rs +++ b/src/blocks/gate/mod.rs @@ -203,9 +203,7 @@ impl Component for TappedGate { params: &Self::Params, _ctx: &substrate::data::SubstrateCtx, ) -> substrate::error::Result { - Ok(TappedGate { - params: params.clone(), - }) + Ok(TappedGate { params: *params }) } fn name(&self) -> arcstr::ArcStr { @@ -544,6 +542,27 @@ mod tests { }) .expect("failed to run pex"); + let pu_zin_work_dir = work_dir.join("pu_zin_sim"); + let pu_zin = ctx + .write_simulation::>( + &AcImpedanceTbParams { + vdd: 1.8, + fstart: 100., + fstop: 100e6, + points: 10, + dut: params, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vss, + connections: HashMap::from_iter([ + (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("a"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("y"), vec![AcImpedanceTbNode::Floating]), + ]), + }, + &pu_zin_work_dir, + ) + .expect("failed to write simulation"); let pu_zout_work_dir = work_dir.join("pu_zout_sim"); let pu_zout = ctx .write_simulation::>( @@ -554,6 +573,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Floating, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), @@ -565,11 +585,33 @@ mod tests { ) .expect("failed to write simulation"); println!( - "Pull-up: Cout = {}, Rout = {}", + "Pull-up: Cin = {}, Cout = {}, Rout = {}", + pu_zin.max_freq_cap(), pu_zout.max_freq_cap(), pu_zout.min_freq_res() ); + let pd_zin_work_dir = work_dir.join("pd_zin_sim"); + let pd_zin = ctx + .write_simulation::>( + &AcImpedanceTbParams { + vdd: 1.8, + fstart: 100., + fstop: 100e6, + points: 10, + dut: params, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vdd, + connections: HashMap::from_iter([ + (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("a"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("y"), vec![AcImpedanceTbNode::Floating]), + ]), + }, + &pd_zin_work_dir, + ) + .expect("failed to write simulation"); let pd_zout_work_dir = work_dir.join("pd_zout_sim"); let pd_zout = ctx .write_simulation::>( @@ -580,6 +622,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Floating, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), @@ -591,7 +634,8 @@ mod tests { ) .expect("failed to write simulation"); println!( - "Pull-down: Cout = {}, Rout = {}", + "Pull-down: Cin = {}, Cout = {}, Rout = {}", + pd_zin.max_freq_cap(), pd_zout.max_freq_cap(), pd_zout.min_freq_res() ); @@ -794,6 +838,28 @@ mod tests { }) .expect("failed to run pex"); + let pu_zin_work_dir = work_dir.join("pu_zin_sim"); + let pu_zin = ctx + .write_simulation::>( + &AcImpedanceTbParams { + vdd: 1.8, + fstart: 100., + fstop: 100e6, + points: 10, + dut: params, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vss, + connections: HashMap::from_iter([ + (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("a"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("b"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("y"), vec![AcImpedanceTbNode::Floating]), + ]), + }, + &pu_zin_work_dir, + ) + .expect("failed to write simulation"); let pu_zout_work_dir = work_dir.join("pu_zout_sim"); let pu_zout = ctx .write_simulation::>( @@ -804,6 +870,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Floating, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), @@ -816,11 +883,34 @@ mod tests { ) .expect("failed to write simulation"); println!( - "Pull-up: Cout = {}, Rout = {}", + "Pull-up: Cin = {}, Cout = {}, Rout = {}", + pu_zin.max_freq_cap(), pu_zout.max_freq_cap(), pu_zout.min_freq_res() ); + let pd_zin_work_dir = work_dir.join("pd_zin_sim"); + let pd_zin = ctx + .write_simulation::>( + &AcImpedanceTbParams { + vdd: 1.8, + fstart: 100., + fstop: 100e6, + points: 10, + dut: params, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vdd, + connections: HashMap::from_iter([ + (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("a"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("b"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("y"), vec![AcImpedanceTbNode::Floating]), + ]), + }, + &pd_zin_work_dir, + ) + .expect("failed to write simulation"); let pd_zout_work_dir = work_dir.join("pd_zout_sim"); let pd_zout = ctx .write_simulation::>( @@ -831,6 +921,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Floating, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), @@ -843,7 +934,8 @@ mod tests { ) .expect("failed to write simulation"); println!( - "Pull-down: Cout = {}, Rout = {}", + "Pull-down: Cin = {}, Cout = {}, Rout = {}", + pd_zin.max_freq_cap(), pd_zout.max_freq_cap(), pd_zout.min_freq_res() ); @@ -907,6 +999,29 @@ mod tests { }) .expect("failed to run pex"); + let pu_zin_work_dir = work_dir.join("pu_zin_sim"); + let pu_zin = ctx + .write_simulation::>( + &AcImpedanceTbParams { + vdd: 1.8, + fstart: 100., + fstop: 100e6, + points: 10, + dut: params, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vss, + connections: HashMap::from_iter([ + (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("a"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("b"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("c"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("y"), vec![AcImpedanceTbNode::Floating]), + ]), + }, + &pu_zin_work_dir, + ) + .expect("failed to write simulation"); let pu_zout_work_dir = work_dir.join("pu_zout_sim"); let pu_zout = ctx .write_simulation::>( @@ -917,6 +1032,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Floating, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), @@ -930,11 +1046,35 @@ mod tests { ) .expect("failed to write simulation"); println!( - "Pull-up: Cout = {}, Rout = {}", + "Pull-up: Cin = {}, Cout = {}, Rout = {}", + pu_zin.max_freq_cap(), pu_zout.max_freq_cap(), pu_zout.min_freq_res() ); + let pd_zin_work_dir = work_dir.join("pd_zin_sim"); + let pd_zin = ctx + .write_simulation::>( + &AcImpedanceTbParams { + vdd: 1.8, + fstart: 100., + fstop: 100e6, + points: 10, + dut: params, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vdd, + connections: HashMap::from_iter([ + (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("a"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("b"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("c"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("y"), vec![AcImpedanceTbNode::Floating]), + ]), + }, + &pd_zin_work_dir, + ) + .expect("failed to write simulation"); let pd_zout_work_dir = work_dir.join("pd_zout_sim"); let pd_zout = ctx .write_simulation::>( @@ -945,6 +1085,7 @@ mod tests { points: 10, dut: params, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Floating, connections: HashMap::from_iter([ (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), @@ -958,7 +1099,8 @@ mod tests { ) .expect("failed to write simulation"); println!( - "Pull-down: Cout = {}, Rout = {}", + "Pull-down: Cin = {}, Cout = {}, Rout = {}", + pd_zin.max_freq_cap(), pd_zout.max_freq_cap(), pd_zout.min_freq_res() ); diff --git a/src/blocks/macros/mod.rs b/src/blocks/macros/mod.rs index 31ba6468..071a7d28 100644 --- a/src/blocks/macros/mod.rs +++ b/src/blocks/macros/mod.rs @@ -271,7 +271,6 @@ mod tests { use substrate::component::NoParams; use substrate::schematic::netlist::NetlistPurpose; - use crate::measure::cap::{self, CapTestbench, TbNode}; use crate::paths::{out_gds, out_spice}; use crate::setup_ctx; use crate::tests::test_work_dir; @@ -282,6 +281,10 @@ mod tests { #[cfg(feature = "commercial")] #[ignore = "slow"] fn test_sense_amp_clk_cap() { + use crate::measure::impedance::{ + AcImpedanceTbNode, AcImpedanceTbParams, AcImpedanceTestbench, + }; + let ctx = setup_ctx(); let work_dir = test_work_dir("test_sense_amp_clk_cap"); @@ -317,25 +320,28 @@ mod tests { let sim_work_dir = work_dir.join("sim"); let cap = ctx - .write_simulation::>( - &cap::TbParams { - idc: 10, + .write_simulation::>( + &AcImpedanceTbParams { + fstart: 100., + fstop: 100e6, + points: 10, vdd: 1.8, dut: NoParams, pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vss, connections: HashMap::from_iter([ - (arcstr::literal!("VDD"), vec![TbNode::Vdd]), - (arcstr::literal!("VSS"), vec![TbNode::Vss]), - (arcstr::literal!("clk"), vec![TbNode::Vmeas]), - (arcstr::literal!("inn"), vec![TbNode::Vdd]), - (arcstr::literal!("inp"), vec![TbNode::Vss]), - (arcstr::literal!("outp"), vec![TbNode::Floating]), - (arcstr::literal!("outn"), vec![TbNode::Floating]), + (arcstr::literal!("VDD"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("VSS"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("clk"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("inn"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("inp"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("outp"), vec![AcImpedanceTbNode::Floating]), + (arcstr::literal!("outn"), vec![AcImpedanceTbNode::Floating]), ]), }, &sim_work_dir, ) .expect("failed to write simulation"); - println!("Cclk = {}", cap.cnode); + println!("Cclk = {}", cap.max_freq_cap()); } } diff --git a/src/blocks/rmux/layout.rs b/src/blocks/rmux/layout.rs index 7e83481f..dbfd4892 100644 --- a/src/blocks/rmux/layout.rs +++ b/src/blocks/rmux/layout.rs @@ -121,6 +121,9 @@ impl ReadMux { tracks.push(rect); } + ctx.add_port(CellPort::with_shape("bl", pc.v_metal, tracks[1]))?; + ctx.add_port(CellPort::with_shape("br", pc.v_metal, tracks[2]))?; + let mut metadata = Metadata::builder(); for (port, idx) in [("sd_1_1", 1), ("sd_0_0", 2)] { diff --git a/src/blocks/rmux/mod.rs b/src/blocks/rmux/mod.rs index 74982d42..428ac362 100644 --- a/src/blocks/rmux/mod.rs +++ b/src/blocks/rmux/mod.rs @@ -1,5 +1,11 @@ use serde::Serialize; -use substrate::component::Component; +use substrate::{ + component::Component, + layout::{ + cell::{CellPort, PortConflictStrategy}, + placement::{align::AlignMode, array::ArrayTiler}, + }, +}; mod layout; mod schematic; @@ -26,6 +32,10 @@ pub struct ReadMuxParams { pub idx: usize, } +pub struct TappedReadMux { + pub params: ReadMuxParams, +} + impl Component for ReadMux { type Params = ReadMuxParams; fn new( @@ -113,6 +123,70 @@ impl Component for ReadMuxEnd { } } +impl Component for TappedReadMux { + type Params = ReadMuxParams; + fn new( + params: &Self::Params, + _ctx: &substrate::data::SubstrateCtx, + ) -> substrate::error::Result { + Ok(TappedReadMux { + params: params.clone(), + }) + } + + fn name(&self) -> arcstr::ArcStr { + arcstr::literal!("tapped_read_mux") + } + + fn schematic( + &self, + ctx: &mut substrate::schematic::context::SchematicCtx, + ) -> substrate::error::Result<()> { + let mut gate = ctx.instantiate::(&self.params)?; + ctx.bubble_all_ports(&mut gate); + ctx.add_instance(gate); + Ok(()) + } + + fn layout( + &self, + ctx: &mut substrate::layout::context::LayoutCtx, + ) -> substrate::error::Result<()> { + let params = ReadMuxParams { + idx: 0, + ..self.params + }; + let gate = ctx.instantiate::(¶ms)?; + let tap = ctx.instantiate::(¶ms)?; + let mut tiler = ArrayTiler::builder() + .push(tap) + .push(gate) + .mode(AlignMode::ToTheRight) + .alt_mode(AlignMode::Bottom) + .build(); + tiler.expose_ports( + |port: CellPort, _i| match port.name().as_str() { + "sel_b" => { + if port.id().index() == 0 { + Some(port.with_id("sel_b")) + } else { + None + } + } + "read_bl" => Some(port.with_id("bl_out")), + "read_br" => Some(port.with_id("br_out")), + _ => Some(port), + }, + PortConflictStrategy::Merge, + )?; + ctx.add_ports(tiler.ports().cloned()).unwrap(); + + ctx.draw_ref(&tiler)?; + + Ok(()) + } +} + #[cfg(test)] mod tests { @@ -124,7 +198,7 @@ mod tests { const READ_MUX_PARAMS: ReadMuxParams = ReadMuxParams { length: 150, - width: 2_000, + width: 4_000, mux_ratio: 4, idx: 2, }; @@ -135,8 +209,20 @@ mod tests { let work_dir = test_work_dir("test_read_mux"); ctx.write_layout::(&READ_MUX_PARAMS, out_gds(&work_dir, "layout")) .expect("failed to write layout"); - ctx.write_schematic_to_file::(&READ_MUX_PARAMS, out_spice(work_dir, "schematic")) + ctx.write_schematic_to_file::(&READ_MUX_PARAMS, out_spice(&work_dir, "schematic")) .expect("failed to write schematic"); + + #[cfg(feature = "commercial")] + { + let lvs_work_dir = work_dir.join("lvs"); + let output = ctx + .write_lvs::(&READ_MUX_PARAMS, lvs_work_dir) + .expect("failed to run LVS"); + assert!(matches!( + output.summary, + substrate::verification::lvs::LvsSummary::Pass + )); + } } #[test] @@ -154,4 +240,82 @@ mod tests { ctx.write_layout::(&READ_MUX_PARAMS, out_gds(work_dir, "layout")) .expect("failed to write layout"); } + + #[test] + #[cfg(feature = "commercial")] + #[ignore = "slow"] + fn test_rmux_cap() { + use std::collections::HashMap; + + use substrate::schematic::netlist::NetlistPurpose; + + use crate::measure::impedance::{ + AcImpedanceTbNode, AcImpedanceTbParams, AcImpedanceTestbench, + }; + + let ctx = setup_ctx(); + let work_dir = test_work_dir("test_rmux_cap"); + + let pex_path = out_spice(&work_dir, "pex_schematic"); + let pex_dir = work_dir.join("pex"); + let pex_level = calibre::pex::PexLevel::Rc; + let pex_netlist_path = crate::paths::out_pex(&work_dir, "pex_netlist", pex_level); + ctx.write_schematic_to_file_for_purpose::( + &READ_MUX_PARAMS, + &pex_path, + NetlistPurpose::Pex, + ) + .expect("failed to write pex source netlist"); + let mut opts = std::collections::HashMap::with_capacity(1); + opts.insert("level".into(), pex_level.as_str().into()); + + let gds_path = out_gds(&work_dir, "layout"); + ctx.write_layout::(&READ_MUX_PARAMS, &gds_path) + .expect("failed to write layout"); + + ctx.run_pex(substrate::verification::pex::PexInput { + work_dir: pex_dir, + layout_path: gds_path.clone(), + layout_cell_name: arcstr::literal!("tapped_read_mux"), + layout_format: substrate::layout::LayoutFormat::Gds, + source_paths: vec![pex_path], + source_cell_name: arcstr::literal!("tapped_read_mux"), + pex_netlist_path: pex_netlist_path.clone(), + ground_net: "vss".to_string(), + opts, + }) + .expect("failed to run pex"); + + let selb_work_dir = work_dir.join("selb_sim"); + let cap_selb = ctx + .write_simulation::>( + &AcImpedanceTbParams { + fstart: 100., + fstop: 100e6, + points: 10, + vdd: 1.8, + dut: READ_MUX_PARAMS, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vss, + connections: HashMap::from_iter([ + (arcstr::literal!("sel_b"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("bl"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("br"), vec![AcImpedanceTbNode::Vdd]), + ( + arcstr::literal!("bl_out"), + vec![AcImpedanceTbNode::Floating], + ), + ( + arcstr::literal!("br_out"), + vec![AcImpedanceTbNode::Floating], + ), + (arcstr::literal!("vdd"), vec![AcImpedanceTbNode::Vdd]), + ]), + }, + &selb_work_dir, + ) + .expect("failed to write simulation"); + + println!("Cselb = {}", cap_selb.max_freq_cap(),); + } } diff --git a/src/blocks/wmux/layout.rs b/src/blocks/wmux/layout.rs index 69945b40..aec25ff1 100644 --- a/src/blocks/wmux/layout.rs +++ b/src/blocks/wmux/layout.rs @@ -149,6 +149,9 @@ impl WriteMux { }) .collect::, _>>()?; + ctx.add_port(CellPort::with_shape("bl", pc.v_metal, tracks[0]))?; + ctx.add_port(CellPort::with_shape("br", pc.v_metal, tracks[2]))?; + let target = wmask.port("sd_0_0")?.largest_rect(pc.m0)?; let jog = SJog::builder() .src(tracks[1]) diff --git a/src/blocks/wmux/mod.rs b/src/blocks/wmux/mod.rs index 932baa08..402656a2 100644 --- a/src/blocks/wmux/mod.rs +++ b/src/blocks/wmux/mod.rs @@ -1,5 +1,11 @@ use serde::{Deserialize, Serialize}; -use substrate::component::Component; +use substrate::{ + component::Component, + layout::{ + cell::{CellPort, PortConflictStrategy, PortId}, + placement::{align::AlignMode, array::ArrayTiler}, + }, +}; mod layout; mod schematic; @@ -45,6 +51,10 @@ pub struct WriteMuxEndParams { pub sizing: WriteMuxSizing, } +pub struct TappedWriteMux { + pub sizing: WriteMuxSizing, +} + impl WriteMuxCentParams { pub(crate) fn for_wmux(&self) -> WriteMuxParams { WriteMuxParams { @@ -151,6 +161,69 @@ impl Component for WriteMuxEnd { } } +impl Component for TappedWriteMux { + type Params = WriteMuxSizing; + fn new( + params: &Self::Params, + _ctx: &substrate::data::SubstrateCtx, + ) -> substrate::error::Result { + Ok(TappedWriteMux { sizing: *params }) + } + + fn name(&self) -> arcstr::ArcStr { + arcstr::literal!("tapped_write_mux") + } + + fn schematic( + &self, + ctx: &mut substrate::schematic::context::SchematicCtx, + ) -> substrate::error::Result<()> { + let mut gate = ctx.instantiate::(&WriteMuxParams { + sizing: self.sizing, + idx: 0, + })?; + ctx.bubble_all_ports(&mut gate); + ctx.add_instance(gate); + Ok(()) + } + + fn layout( + &self, + ctx: &mut substrate::layout::context::LayoutCtx, + ) -> substrate::error::Result<()> { + let gate = ctx.instantiate::(&WriteMuxParams { + sizing: self.sizing, + idx: 0, + })?; + let tap = ctx.instantiate::(&WriteMuxEndParams { + sizing: self.sizing, + })?; + let mut tiler = ArrayTiler::builder() + .push(tap) + .push(gate) + .mode(AlignMode::ToTheRight) + .alt_mode(AlignMode::Bottom) + .build(); + tiler.expose_ports( + |port: CellPort, _i| { + if port.id() == &PortId::new("we", 0) { + Some(port.with_id("we")) + } else if port.name() == "we" { + None + } else { + Some(port) + } + }, + PortConflictStrategy::Merge, + )?; + ctx.add_ports(tiler.ports().cloned()).unwrap(); + + ctx.draw_ref(&tiler)?; + + Ok(()) + } +} + #[cfg(test)] mod tests { @@ -187,9 +260,21 @@ mod tests { .expect("failed to write layout"); ctx.write_schematic_to_file::( &WRITE_MUX_PARAMS, - out_spice(work_dir, "schematic"), + out_spice(&work_dir, "schematic"), ) .expect("failed to write schematic"); + + #[cfg(feature = "commercial")] + { + let lvs_work_dir = work_dir.join("lvs"); + let output = ctx + .write_lvs::(&WRITE_MUX_SIZING, lvs_work_dir) + .expect("failed to run LVS"); + assert!(matches!( + output.summary, + substrate::verification::lvs::LvsSummary::Pass + )); + } } #[test] @@ -207,4 +292,111 @@ mod tests { ctx.write_layout::(&WRITE_MUX_END_PARAMS, out_gds(work_dir, "layout")) .expect("failed to write layout"); } + + #[test] + #[cfg(feature = "commercial")] + #[ignore = "slow"] + fn test_wmux_cap() { + use std::collections::HashMap; + + use substrate::schematic::netlist::NetlistPurpose; + + use crate::measure::impedance::{ + AcImpedanceTbNode, AcImpedanceTbParams, AcImpedanceTestbench, + }; + + let ctx = setup_ctx(); + let work_dir = test_work_dir("test_wmux_cap"); + let params = WriteMuxSizing { + length: 150, + mux_width: 8_800, + mux_ratio: 8, + }; + + let pex_path = out_spice(&work_dir, "pex_schematic"); + let pex_dir = work_dir.join("pex"); + let pex_level = calibre::pex::PexLevel::Rc; + let pex_netlist_path = crate::paths::out_pex(&work_dir, "pex_netlist", pex_level); + ctx.write_schematic_to_file_for_purpose::( + ¶ms, + &pex_path, + NetlistPurpose::Pex, + ) + .expect("failed to write pex source netlist"); + let mut opts = std::collections::HashMap::with_capacity(1); + opts.insert("level".into(), pex_level.as_str().into()); + + let gds_path = out_gds(&work_dir, "layout"); + ctx.write_layout::(¶ms, &gds_path) + .expect("failed to write layout"); + + ctx.run_pex(substrate::verification::pex::PexInput { + work_dir: pex_dir, + layout_path: gds_path.clone(), + layout_cell_name: arcstr::literal!("tapped_write_mux"), + layout_format: substrate::layout::LayoutFormat::Gds, + source_paths: vec![pex_path], + source_cell_name: arcstr::literal!("tapped_write_mux"), + pex_netlist_path: pex_netlist_path.clone(), + ground_net: "vss".to_string(), + opts, + }) + .expect("failed to run pex"); + + let we_work_dir = work_dir.join("we_sim"); + let cap_we = ctx + .write_simulation::>( + &AcImpedanceTbParams { + fstart: 100., + fstop: 100e6, + points: 10, + vdd: 1.8, + dut: params.clone(), + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vdd, + connections: HashMap::from_iter([ + (arcstr::literal!("we"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("wmask"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("data"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("data_b"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("bl"), vec![AcImpedanceTbNode::Floating]), + (arcstr::literal!("br"), vec![AcImpedanceTbNode::Floating]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + ]), + }, + &we_work_dir, + ) + .expect("failed to write simulation"); + + let wmask_work_dir = work_dir.join("wmask_sim"); + let cap_wmask = ctx + .write_simulation::>( + &AcImpedanceTbParams { + fstart: 100., + fstop: 100e6, + points: 10, + vdd: 1.8, + dut: params, + pex_netlist: Some(pex_netlist_path.clone()), + vmeas_conn: AcImpedanceTbNode::Vdd, + connections: HashMap::from_iter([ + (arcstr::literal!("we"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("wmask"), vec![AcImpedanceTbNode::Vmeas]), + (arcstr::literal!("data"), vec![AcImpedanceTbNode::Vss]), + (arcstr::literal!("data_b"), vec![AcImpedanceTbNode::Vdd]), + (arcstr::literal!("bl"), vec![AcImpedanceTbNode::Floating]), + (arcstr::literal!("br"), vec![AcImpedanceTbNode::Floating]), + (arcstr::literal!("vss"), vec![AcImpedanceTbNode::Vss]), + ]), + }, + &wmask_work_dir, + ) + .expect("failed to write simulation"); + + println!( + "Cwe = {}, Cwmask = {}", + cap_we.max_freq_cap(), + cap_wmask.max_freq_cap() + ); + } } diff --git a/src/measure/impedance.rs b/src/measure/impedance.rs index d59e6ad3..42443618 100644 --- a/src/measure/impedance.rs +++ b/src/measure/impedance.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use substrate::component::Component; use substrate::schematic::circuit::Direction; use substrate::schematic::elements::iac::Iac; +use substrate::schematic::elements::resistor::Resistor; use substrate::schematic::elements::vdc::Vdc; use substrate::schematic::elements::vpulse::Vpulse; use substrate::schematic::signal::Signal; @@ -228,6 +229,7 @@ pub struct AcImpedanceTbParams { pub points: usize, pub dut: T, pub pex_netlist: Option, + pub vmeas_conn: AcImpedanceTbNode, pub connections: HashMap>, } @@ -266,6 +268,34 @@ impl> Component for AcImpedanceTe let vmeas = ctx.signal("vmeas"); let mut ctr = 0; + match self.params.vmeas_conn { + AcImpedanceTbNode::Vdd => { + ctx.instantiate::(&SiValue::new(100, SiPrefix::Mega))? + .with_connections([("p", vmeas), ("n", vdd)]) + .named("Vmeas") + .add_to(ctx); + } + AcImpedanceTbNode::Vss => { + ctx.instantiate::(&SiValue::new(100, SiPrefix::Mega))? + .with_connections([("p", vmeas), ("n", vss)]) + .named("Vmeas") + .add_to(ctx); + } + AcImpedanceTbNode::Vcustom(val) => { + ctr += 1; + let vcustom = ctx.signal(format!("custom{ctr}")); + ctx.instantiate::(&val)? + .with_connections([("p", vcustom), ("n", vss)]) + .named("Vmeas") + .add_to(ctx); + ctx.instantiate::(&SiValue::new(100, SiPrefix::Mega))? + .with_connections([("p", vmeas), ("n", vcustom)]) + .named("Vmeas") + .add_to(ctx); + } + _ => {} + }; + let mut connections = Vec::new(); for (k, nodes) in &self.params.connections { @@ -276,6 +306,7 @@ impl> Component for AcImpedanceTe AcImpedanceTbNode::Vss => vss, AcImpedanceTbNode::Vmeas => vmeas, AcImpedanceTbNode::Vcustom(val) => { + ctr += 1; let vcustom = ctx.signal(format!("custom{ctr}")); ctx.instantiate::(val)? .with_connections([("p", vcustom), ("n", vss)])