diff --git a/docs/designv3.md b/docs/designv3.md new file mode 100644 index 00000000..c8a7f21e --- /dev/null +++ b/docs/designv3.md @@ -0,0 +1,93 @@ +Changes: +Separate wen/ren (should have defined behavior if both are high) +All SR latches, FFs, etc should have a reset signal +Write drivers should be inverters, not just single pull-down transistors +Write mux should be transmission gate rather than NMOS +Unify write/read mux +4-way banking, with control logic in the middle +Everything manually routed except non-scaling control logic +Divided WL + divided BL architecture +Top and bottom banks +During R/W, pick either top or bottom bank based on MSB of addr +Always read from both left+right halves of array +Col circuitry shared between top/bottom +latch input/output/inout pins +testbench should read/write every addr in pex sim and check internal state of sram bitcells +Write wordline pulse width controlled by inverter chain +Wordline gating happens after decoder + +Open questions: +Why should there be a separate read/write mux +How to size predecoders +How to partition addr bits +How to layout predecoders +Where to place rbl +Do we need to support R90 variants? +Write assist circuitry? +Before turning on wl_en, must ensure predecoders/decoder have finished decoding. How should we figure out when to turn on wl_en? How should we minimize the time between when wl_en actually turns on and the theoretical earliest time at which wl_en could turn on? Do we assume that the delay through the driver chain for wl_en to all final stage gates is approximately equal to the decoder delay? + +Bitcell organization: +4 banks: ul, ur, ll, lr +Words split across left and right banks +Only one of upper and lower banks activated at any time +Control logic inputs: +ce (chip enable) +we (write enable) +clk +reset +Control logic outputs: +sae +pc_b +colsel[i]/colsel_b[i] +wlen (rwl) +wrdrven + +Read sequence of operations: +Latch addr/din +Disable precharge, sae +Decode address (turn on appropriate read mux config) +Turn on wlen pulse +Turn off wlen pulse once replica bitline reaches VDD/2 +Send sae to trigger sense amp +Send sense amp outputs to DFFs/latches +Enable precharge + +Write sequence of operations: +Latch addr/din +Disable precharge, sae +Decode address (turn on appropriate write mux config) +Turn on wrdrven/wlen pulse such that both worldline and bitline are driven at same time +Turn off wlen/wrdrven pulse after some inverter chain delay +Enable precharge + +Control logic implementation: +SR latch for pc_b +Set at positive edge of clock +Reset inverter chain after sae goes high OR immediately after wlen goes low +SR latch for sae +Set when wlen goes low during read operation +Reset at positive edge of clock + +```verilog +module controlv3 ( +input ce, we, clk, reset, +output sae, pc_b, wlen, wrdrven, +inout rbl +) + wire clkp; + edge_detector clk_edge_detector(.clk(clk & ce), .clkp(clkp)); + + sr_latch pc_latch(.s(sae #4 | reset), .r(clkp), .q(pc_b)); + sr_latch sae_latch(.s(we_b & wlen_decoder), .r(clkp | reset), .q(sae)); + + wire wlen_set, wlen_rst; + assign wlen_set = clkp; + assign wlen_rst = we ? clkp #4 : rbl_b; + sr_latch wlen_latch(.s(wlen_set), .r(wlen_rst | reset), .q(wlen)); + + wire wrdrven_rst; + decoder_replica decoder_replica(.a(wlen_rst), .o(wlen_decoder)); + sr_latch wrdrven_latch(.s(clkp & we), .r(wlen_decoder | reset), .q(wrdrven)); + +endmodule +``` diff --git a/src/blocks/decoder/mod.rs b/src/blocks/decoder/mod.rs index f1fd2923..09a363aa 100644 --- a/src/blocks/decoder/mod.rs +++ b/src/blocks/decoder/mod.rs @@ -1,19 +1,23 @@ -use serde::{Deserialize, Serialize}; -use substrate::component::{Component, NoParams}; -use substrate::index::IndexOwned; -use substrate::schematic::circuit::Direction; - use self::layout::{ decoder_stage_layout, LastBitDecoderPhysicalDesignScript, PredecoderPhysicalDesignScript, RoutingStyle, }; +use crate::blocks::decoder::sizing::{path_map_tree, Tree, ValueTree}; +use serde::{Deserialize, Serialize}; +use subgeom::snap_to_grid; +use substrate::component::{Component, NoParams}; +use substrate::index::IndexOwned; +use substrate::logic::delay::{GateModel, LogicPath, OptimizerOpts}; +use substrate::schematic::circuit::Direction; -use super::gate::{AndParams, Gate, GateParams, GateType, PrimitiveGateParams}; +use super::gate::{AndParams, Gate, GateParams, GateType, PrimitiveGateParams, PrimitiveGateType}; pub mod layout; pub mod schematic; pub mod sim; +pub mod sizing; + pub struct Decoder { params: DecoderParams, } @@ -49,6 +53,7 @@ pub struct TreeNode { // Number of one-hot outputs. pub num: usize, pub children: Vec, + pub child_nums: Vec, } #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] @@ -275,91 +280,242 @@ impl Component for WmuxDriver { } impl DecoderTree { - pub fn for_columns(bits: usize, top_scale: i64) -> Self { - let plan = plan_decoder(bits, true, false, true); - let mut root = size_decoder(&plan); - root.gate = root.gate.scale(top_scale); + pub fn new(bits: usize, cload: f64) -> Self { + let plan = plan_decoder(bits, true, false, false); + let mut root = size_decoder(&plan, cload); DecoderTree { root } } +} - pub fn with_scale_and_skew(bits: usize, top_scale: i64, skew_rising: bool) -> Self { - let plan = plan_decoder(bits, true, skew_rising, false); - let mut root = size_decoder(&plan); - root.gate = root.gate.scale(top_scale); - DecoderTree { root } +fn size_decoder(tree: &PlanTreeNode, cwl: f64) -> TreeNode { + path_map_tree(tree, &size_path, &cwl) +} + +/// The on-resistance and capacitances of a 1x inverter ([`INV_PARAMS`]). +pub(crate) const INV_MODEL: GateModel = GateModel { + res: 1422.118502462849, + cin: 0.000000000000004482092764998187, + cout: 0.0000000004387405174617657, +}; + +/// The on-resistance and capacitances of a 1x NAND2 gate ([`NAND2_PARAMS`]). +pub(crate) const NAND2_MODEL: GateModel = GateModel { + res: 1478.364147093855, + cin: 0.000000000000005389581112035269, + cout: 0.0000000002743620195248461, +}; + +/// The on-resistance and capacitances of a 1x NAND3 gate ([`NAND3_PARAMS`]). +pub(crate) const NAND3_MODEL: GateModel = GateModel { + res: 1478.037783669641, + cin: 0.000000000000006217130454627972, + cout: 0.000000000216366152882086, +}; + +/// The on-resistance and capacitances of a 1x NOR2 gate ([`NOR2_PARAMS`]). +pub(crate) const NOR2_MODEL: GateModel = GateModel { + res: 1.0, + cin: 1.0, + cout: 1.0, +}; + +/// The sizing of a 1x inverter. +pub(crate) const INV_PARAMS: PrimitiveGateParams = PrimitiveGateParams { + nwidth: 1_000, + pwidth: 2_500, + length: 150, +}; + +/// The sizing of a 1x NAND2 gate. +pub(crate) const NAND2_PARAMS: PrimitiveGateParams = PrimitiveGateParams { + nwidth: 2_000, + pwidth: 2_500, + length: 150, +}; + +/// The sizing of a 1x NAND3 gate. +pub(crate) const NAND3_PARAMS: PrimitiveGateParams = PrimitiveGateParams { + nwidth: 3_000, + pwidth: 2_500, + length: 150, +}; + +/// The sizing of a 1x NOR2 gate. +pub(crate) const NOR2_PARAMS: PrimitiveGateParams = PrimitiveGateParams { + nwidth: 1_000, + pwidth: 3_200, + length: 150, +}; + +pub(crate) fn gate_params(gate: GateType) -> PrimitiveGateParams { + match gate { + GateType::Inv => INV_PARAMS, + GateType::Nand2 => NAND2_PARAMS, + GateType::Nand3 => NAND3_PARAMS, + GateType::Nor2 => NOR2_PARAMS, + gate => panic!("unsupported gate type: {gate:?}"), } +} - #[inline] - pub fn with_scale(bits: usize, top_scale: i64) -> Self { - Self::with_scale_and_skew(bits, top_scale, false) +pub(crate) fn primitive_gate_params(gate: PrimitiveGateType) -> PrimitiveGateParams { + match gate { + PrimitiveGateType::Inv => INV_PARAMS, + PrimitiveGateType::Nand2 => NAND2_PARAMS, + PrimitiveGateType::Nand3 => NAND3_PARAMS, + PrimitiveGateType::Nor2 => NOR2_PARAMS, } +} - #[inline] - pub fn with_skew(bits: usize, skew_rising: bool) -> Self { - Self::with_scale_and_skew(bits, 1, skew_rising) +pub(crate) fn gate_model(gate: GateType) -> GateModel { + match gate { + GateType::Inv => INV_MODEL, + GateType::Nand2 => NAND2_MODEL, + GateType::Nand3 => NAND3_MODEL, + GateType::Nor2 => NOR2_MODEL, + gate => panic!("unsupported gate type: {gate:?}"), } +} - #[inline] - pub fn new(bits: usize) -> Self { - Self::with_scale_and_skew(bits, 1, false) +pub(crate) fn primitive_gate_model(gate: PrimitiveGateType) -> GateModel { + match gate { + PrimitiveGateType::Inv => INV_MODEL, + PrimitiveGateType::Nand2 => NAND2_MODEL, + PrimitiveGateType::Nand3 => NAND3_MODEL, + PrimitiveGateType::Nor2 => NOR2_MODEL, } } -fn size_decoder(tree: &PlanTreeNode) -> TreeNode { - // TODO improve decoder sizing - size_helper_tmp(tree, tree.skew_rising, tree.cols) +pub(crate) fn scale(gate: PrimitiveGateParams, scale: f64) -> PrimitiveGateParams { + let nwidth = snap_to_grid((gate.nwidth as f64 * scale).round() as i64, 50); + let pwidth = snap_to_grid((gate.pwidth as f64 * scale).round() as i64, 50); + PrimitiveGateParams { + nwidth, + pwidth, + length: gate.length, + } } -fn size_helper_tmp(x: &PlanTreeNode, skew_rising: bool, cols: bool) -> TreeNode { - let gate_params = if cols { - AndParams { - nand: PrimitiveGateParams { - nwidth: 10_000, - pwidth: 8_000, - length: 150, - }, - inv: PrimitiveGateParams { - nwidth: 8_000, - pwidth: 10_000, - length: 150, - }, +fn size_path(path: &[&PlanTreeNode], end: &f64) -> TreeNode { + let mut lp = LogicPath::new(); + let mut vars = Vec::new(); + for (i, node) in path.iter().copied().rev().enumerate() { + for (j, gate) in node.gate.primitive_gates().iter().copied().enumerate() { + if i == 0 && j == 0 { + lp.append_sized_gate(gate_model(gate)); + println!("fanout = {:.3}", end / gate_model(gate).cin); + println!("append sized {gate:?}"); + } else { + let var = lp.create_variable_with_initial(2.); + let model = gate_model(gate); + if i != 0 && j == 0 { + let branching = node.num / node.children[0].num - 1; + if branching > 0 { + let mult = branching as f64 * model.cin; + assert!(mult >= 0.0, "mult must be larger than zero, got {mult}"); + lp.append_variable_capacitor(mult, var); + println!("append variable cap {branching:?}x"); + } + } + lp.append_unsized_gate(model, var); + println!("append unsized {gate:?}"); + vars.push(var); + } } - } else if skew_rising { - AndParams { - nand: PrimitiveGateParams { - nwidth: 4_000, - pwidth: 1_000, - length: 150, - }, - inv: PrimitiveGateParams { - nwidth: 600, - pwidth: 6_800, - length: 150, - }, - } - } else { - AndParams { - nand: PrimitiveGateParams { - nwidth: 2_400, - pwidth: 800, - length: 150, - }, - inv: PrimitiveGateParams { - nwidth: 3_100, - pwidth: 4_300, - length: 150, - }, + } + lp.append_capacitor(*end); + + lp.size_with_opts(OptimizerOpts { + lr: 1e11, + lr_decay: 0.999995, + max_iter: 10_000_000, + }); + + let mut cnode: Option<&mut TreeNode> = None; + let mut tree = None; + + let mut values = vars + .iter() + .rev() + .map(|v| { + let v = lp.value(*v); + assert!(v >= 0.5, "gate scale must be at least 0.5, got {v:.3}"); + v + }) + .collect::>(); + values.push(1.); + println!("values = {values:?}"); + let mut values = values.into_iter(); + + for &node in path { + let gate = match node.gate { + GateType::And2 => GateParams::And2(AndParams { + inv: scale(INV_PARAMS, values.next().unwrap()), + nand: scale(NAND2_PARAMS, values.next().unwrap()), + }), + GateType::And3 => GateParams::And3(AndParams { + inv: scale(INV_PARAMS, values.next().unwrap()), + nand: scale(NAND3_PARAMS, values.next().unwrap()), + }), + GateType::Inv => GateParams::Inv(scale(INV_PARAMS, values.next().unwrap())), + GateType::Nand2 => GateParams::Nand2(scale(NAND2_PARAMS, values.next().unwrap())), + GateType::Nand3 => GateParams::Nand3(scale(NAND3_PARAMS, values.next().unwrap())), + GateType::Nor2 => GateParams::Nor2(scale(NOR2_PARAMS, values.next().unwrap())), + }; + + let n = TreeNode { + gate, + num: node.num, + children: vec![], + child_nums: node.children.iter().map(|n| n.num).collect(), + }; + + if let Some(parent) = cnode { + parent.children.push(n); + cnode = Some(&mut parent.children[0]) + } else { + tree = Some(n); + cnode = Some(tree.as_mut().unwrap()); } - }; - // TODO size decoder - TreeNode { - gate: GateParams::new_and(x.gate, gate_params), - num: x.num, - children: x - .children - .iter() - .map(|n| size_helper_tmp(n, skew_rising, cols)) - .collect::>(), + } + + tree.unwrap() +} + +impl Tree for PlanTreeNode { + fn children(&self) -> &[Self] { + &self.children + } + + fn children_mut(&mut self) -> &mut [Self] { + &mut self.children + } + + fn add_right_child(&mut self, child: Self) { + self.children.push(child); + } +} + +impl Tree for TreeNode { + fn children(&self) -> &[Self] { + &self.children + } + + fn children_mut(&mut self) -> &mut [Self] { + &mut self.children + } + + fn add_right_child(&mut self, child: Self) { + self.children.push(child); + } +} + +impl ValueTree for TreeNode { + fn value_for_child(&self, idx: usize) -> f64 { + let first_gate_type = self.gate.gate_type().primitive_gates()[0]; + let first_gate = self.gate.first_gate_sizing(); + let model = gate_model(first_gate_type); + (self.num / self.child_nums[idx]) as f64 * model.cin * first_gate.nwidth as f64 + / (gate_params(first_gate_type).nwidth as f64) } } @@ -393,12 +549,30 @@ fn plan_decoder(bits: usize, top: bool, skew_rising: bool, cols: bool) -> PlanTr .into_iter() .map(|x| plan_decoder(x, false, skew_rising, cols)) .collect::>(); - PlanTreeNode { + let node = PlanTreeNode { gate, num: 2usize.pow(bits as u32), children, skew_rising, cols, + }; + + if top { + PlanTreeNode { + gate: GateType::Inv, + num: node.num, + children: vec![PlanTreeNode { + gate: GateType::Inv, + num: node.num, + children: vec![node], + skew_rising, + cols, + }], + skew_rising, + cols, + } + } else { + node } } } @@ -407,8 +581,8 @@ fn partition_bits(bits: usize, top: bool) -> Vec { assert!(bits > 3); if top { - let left = bits / 2; - return vec![left, bits - left]; + let right = bits / 2; + return vec![bits - right, right]; } if bits % 2 == 0 { @@ -421,8 +595,8 @@ fn partition_bits(bits: usize, top: bool) -> Vec { _ => panic!("unexpected remainder of `bits` divided by 3"), } } else { - let left = bits / 2; - vec![left, bits - left] + let right = bits / 2; + vec![bits - right, right] } } @@ -541,7 +715,7 @@ mod tests { let ctx = setup_ctx(); let work_dir = test_work_dir("test_decoder_4bit"); - let tree = DecoderTree::new(4); + let tree = DecoderTree::new(4, 150e-15); let params = DecoderParams { tree }; ctx.write_schematic_to_file::(¶ms, out_spice(work_dir, "netlist")) @@ -608,7 +782,7 @@ mod tests { let ctx = setup_ctx(); let work_dir = test_work_dir("test_predecoder_4"); - let tree = DecoderTree::new(4); + let tree = DecoderTree::new(4, 150e-15); let params = DecoderParams { tree }; ctx.write_layout::(¶ms, out_gds(work_dir, "layout")) @@ -620,7 +794,7 @@ mod tests { let ctx = setup_ctx(); let work_dir = test_work_dir("test_predecoder_6"); - let tree = DecoderTree::new(6); + let tree = DecoderTree::new(6, 150e-15); let params = DecoderParams { tree }; ctx.write_layout::(¶ms, out_gds(&work_dir, "layout")) diff --git a/src/blocks/decoder/sim.rs b/src/blocks/decoder/sim.rs index 6019d24d..a780bfec 100644 --- a/src/blocks/decoder/sim.rs +++ b/src/blocks/decoder/sim.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use super::{Decoder, DecoderParams, DecoderTree}; +use crate::blocks::sram::WORDLINE_CAP_PER_CELL; use serde::{Deserialize, Serialize}; use substrate::component::Component; use substrate::index::IndexOwned; @@ -11,8 +13,6 @@ use substrate::verification::simulation::testbench::Testbench; use substrate::verification::simulation::waveform::Waveform; use substrate::verification::simulation::{OutputFormat, TranAnalysis}; -use super::{Decoder, DecoderParams, DecoderTree}; - #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct DecoderCriticalPathTbParams { bits: usize, @@ -55,7 +55,7 @@ impl Component for DecoderCriticalPathTb { .with_connections([("p", vdd), ("n", vss)]) .add_to(ctx); - let tree = DecoderTree::with_scale(params.bits, params.scale); + let tree = DecoderTree::new(params.bits, 64. * WORDLINE_CAP_PER_CELL); let decoder_params = DecoderParams { tree }; ctx.instantiate::(&decoder_params)? .with_connections([ diff --git a/src/blocks/decoder/sizing.rs b/src/blocks/decoder/sizing.rs index e0a2f391..78591e78 100644 --- a/src/blocks/decoder/sizing.rs +++ b/src/blocks/decoder/sizing.rs @@ -1,56 +1,200 @@ -use super::*; +use std::fmt::Debug; -fn size_decoder(tree: &PlanTreeNode) -> TreeNode { - let mut f = FanoutAnalyzer::new(); +pub trait Tree: Sized { + fn children(&self) -> &[Self]; + fn children_mut(&mut self) -> &mut [Self]; - let mut nodes = vec![]; - let mut curr = Some(tree); - while let Some(node) = curr { - nodes.push(node); - curr = node.children.get(0); - } - nodes.reverse(); + fn add_right_child(&mut self, child: Self); +} - for (i, node) in nodes.iter().enumerate() { - for gate in node.gate.as_fanout_gates() { - f.add_gate(gate); - } - if let Some(next) = nodes.get(i + 1) { - f.add_branch((next.num / node.num) as f64); +pub trait ValueTree: Tree { + fn value_for_child(&self, idx: usize) -> V; +} + +pub fn path_map_tree + Debug, F, V>( + tree: &T, + map: &F, + end: &V, +) -> U +where + F: Fn(&[&T], &V) -> U, +{ + let path = left_path(tree); + let mut mapped_path = map(&path, end); + assert_eq!(left_path_len(&mapped_path), path.len()); + assert_eq!(tree.children().len(), mapped_path.children().len()); + + let mut state = Some((&mut mapped_path, path[0])); + while let Some((out, input)) = state { + for (i, tree) in input.children().iter().enumerate().skip(1) { + let subtree = path_map_tree(tree, map, &out.value_for_child(i)); + out.add_right_child(subtree); } + state = input + .children() + .first() + .map(|child| (&mut out.children_mut()[0], child)); } - // TODO use fanout results - let res = f.size(32f64); - let mut sizes = res.sizes().collect::>(); - sizes.reverse(); - - size_helper_tmp(tree, &sizes, tree.skew_rising) + mapped_path } -struct SizingParams { - lch: i64, - /// Logical effort of a NAND2 gate. - g_nand2: f64, - /// Logical effort of a NAND3 gate. - g_nand3: f64, - /// Input capacitance of a unit inverter. - c1: f64, +fn left_path(tree: &T) -> Vec<&T> { + let mut nodes = Vec::new(); + let mut root = Some(tree); + while let Some(node) = root { + nodes.push(node); + root = node.children().first(); + } + nodes } -fn size_path(path: &[&PlanTreeNode]) -> Vec { - +fn left_path_len(tree: &T) -> usize { + let mut nodes = Vec::new(); + let mut root = Some(tree); + let mut count = 0; + while let Some(node) = root { + nodes.push(node); + root = node.children().first(); + count += 1; + } + count } #[cfg(test)] mod tests { use super::*; - #[test] - fn plan() { - for bits in [5, 8, 10] { - let tree = plan_decoder(bits, true, true); - println!("{:?}", tree); + #[derive(Debug, Default, Eq, PartialEq)] + struct TreeNode { + children: Vec, + } + + #[derive(Debug, Default, Eq, PartialEq)] + struct ValueTreeNode { + value: i32, + children: Vec, + } + + impl Tree for TreeNode { + fn children(&self) -> &[Self] { + &self.children + } + + fn children_mut(&mut self) -> &mut [Self] { + &mut self.children } + + fn add_right_child(&mut self, child: Self) { + self.children.push(child) + } + } + + impl Tree for ValueTreeNode { + fn children(&self) -> &[Self] { + &self.children + } + + fn children_mut(&mut self) -> &mut [Self] { + &mut self.children + } + + fn add_right_child(&mut self, child: Self) { + self.children.push(child) + } + } + + impl ValueTreeNode { + pub fn new(value: i32) -> Self { + Self { + children: vec![], + value, + } + } + } + + impl ValueTree for ValueTreeNode { + fn value_for_child(&self, _: usize) -> i32 { + self.value + } + } + + fn map(input: &[&TreeNode], value: &i32) -> ValueTreeNode { + assert!(!input.is_empty()); + + let mut value = *value; + value *= 2; + + let mut tree = ValueTreeNode { + children: vec![], + value, + }; + + let mut root = &mut tree; + for _ in &input[1..] { + value *= 2; + root.add_right_child(ValueTreeNode { + children: vec![], + value, + }); + root = &mut root.children_mut()[0]; + } + + tree + } + + #[test] + fn test_path_map_tree_simple() { + let input = TreeNode { + children: vec![TreeNode::default(), TreeNode::default()], + }; + + let output = ValueTreeNode { + value: 2, + children: vec![ValueTreeNode::new(4), ValueTreeNode::new(4)], + }; + + let tree = path_map_tree(&input, &map, &1); + assert_eq!(tree, output); + } + #[test] + fn test_path_map_tree() { + let input = TreeNode { + children: vec![ + TreeNode { + children: vec![TreeNode::default(), TreeNode::default()], + }, + TreeNode { + children: vec![ + TreeNode::default(), + TreeNode::default(), + TreeNode::default(), + ], + }, + TreeNode::default(), + ], + }; + + let output = ValueTreeNode { + value: 2, + children: vec![ + ValueTreeNode { + value: 4, + children: vec![ValueTreeNode::new(8), ValueTreeNode::new(8)], + }, + ValueTreeNode { + value: 4, + children: vec![ + ValueTreeNode::new(8), + ValueTreeNode::new(8), + ValueTreeNode::new(8), + ], + }, + ValueTreeNode::new(4), + ], + }; + + let tree = path_map_tree(&input, &map, &1); + assert_eq!(tree, output); } } diff --git a/src/blocks/gate/mod.rs b/src/blocks/gate/mod.rs index 1bb01ff1..1fb64063 100644 --- a/src/blocks/gate/mod.rs +++ b/src/blocks/gate/mod.rs @@ -7,11 +7,14 @@ use substrate::layout::layers::selector::Selector; use substrate::layout::placement::align::AlignMode; use substrate::layout::placement::array::ArrayTiler; -use super::decoder::layout::{DecoderGate, DecoderGateParams, DecoderTap}; -use super::decoder::{self}; +use super::decoder::layout::{ + decoder_stage_layout, DecoderGate, DecoderGateParams, DecoderTap, RoutingStyle, +}; +use super::decoder::{self, DecoderStageParams}; pub mod layout; pub mod schematic; +pub mod sizing; pub enum Gate { And2(And2), @@ -76,6 +79,14 @@ pub enum GateType { Nor2, } +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)] +pub enum PrimitiveGateType { + Inv, + Nand2, + Nand3, + Nor2, +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)] pub struct PrimitiveGateParams { pub nwidth: i64, @@ -83,6 +94,19 @@ pub struct PrimitiveGateParams { pub length: i64, } +impl GateType { + pub fn primitive_gates(&self) -> Vec { + match *self { + GateType::And2 => vec![GateType::Nand2, GateType::Inv], + GateType::And3 => vec![GateType::Nand3, GateType::Inv], + GateType::Inv => vec![GateType::Inv], + GateType::Nand2 => vec![GateType::Nand2], + GateType::Nand3 => vec![GateType::Nand3], + GateType::Nor2 => vec![GateType::Nor2], + } + } +} + impl PrimitiveGateParams { pub fn scale(&self, factor: i64) -> Self { Self { @@ -142,6 +166,39 @@ impl GateParams { GateParams::Nor2(x) => Self::Nor2(x.scale(factor)), } } + + pub fn gate_type(&self) -> GateType { + match self { + GateParams::And2(_) => GateType::And2, + GateParams::And3(_) => GateType::And3, + GateParams::Inv(_) => GateType::Inv, + GateParams::Nand2(_) => GateType::Nand2, + GateParams::Nand3(_) => GateType::Nand3, + GateParams::Nor2(_) => GateType::Nor2, + } + } + + pub fn first_gate_sizing(&self) -> PrimitiveGateParams { + match self { + GateParams::And2(a) => a.nand, + GateParams::And3(a) => a.nand, + GateParams::Inv(x) => *x, + GateParams::Nand2(x) => *x, + GateParams::Nand3(x) => *x, + GateParams::Nor2(x) => *x, + } + } + + pub fn last_gate_sizing(&self) -> PrimitiveGateParams { + match self { + GateParams::And2(a) => a.inv, + GateParams::And3(a) => a.inv, + GateParams::Inv(x) => *x, + GateParams::Nand2(x) => *x, + GateParams::Nand3(x) => *x, + GateParams::Nor2(x) => *x, + } + } } macro_rules! call_gate_fn { diff --git a/src/blocks/gate/sizing.rs b/src/blocks/gate/sizing.rs new file mode 100644 index 00000000..90b9ae68 --- /dev/null +++ b/src/blocks/gate/sizing.rs @@ -0,0 +1,194 @@ +use crate::blocks::decoder::sizing::{Tree, ValueTree}; +use crate::blocks::decoder::{ + gate_model, gate_params, primitive_gate_model, primitive_gate_params, TreeNode, +}; +use crate::blocks::gate::{ + AndParams, Gate, GateParams, GateType, PrimitiveGateParams, PrimitiveGateType, +}; +use serde::{Deserialize, Serialize}; +use substrate::logic::delay::{LogicPath, OptimizerOpts}; + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct InverterGateTreeNode { + gate: PrimitiveGateType, + id: u64, + /// The number of inverters placed after `gate`. + n_invs: usize, + /// The number of gates in the next stage + /// that the final gate associated to this node drives. + n_branching: usize, + children: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct GateTreeNode { + gate: PrimitiveGateType, + id: u64, + /// The number of gates in the next stage + /// that the final gate associated to this node drives. + n_branching: usize, + children: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct SizedGateTreeNode { + gate: PrimitiveGateParams, + gate_type: PrimitiveGateType, + id: u64, + /// The number of gates in the next stage + /// that the final gate associated to this node drives. + n_branching: usize, + children: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GateTree { + root: GateTreeNode, + load_cap: f64, +} + +impl InverterGateTreeNode { + pub fn elaborate(&self) -> GateTreeNode { + elaborate_inner(self, self.n_invs, self.n_branching) + } +} + +fn elaborate_inner(node: &InverterGateTreeNode, n_invs: usize, n_branching: usize) -> GateTreeNode { + if n_invs == 0 { + GateTreeNode { + gate: node.gate, + id: node.id, + n_branching, + children: node.children.iter().map(|n| n.elaborate()).collect(), + } + } else { + GateTreeNode { + gate: PrimitiveGateType::Inv, + id: node.id, + n_branching, + children: vec![elaborate_inner(node, n_invs - 1, 1)], + } + } +} + +fn size_path(path: &[&GateTreeNode], end: &f64) -> SizedGateTreeNode { + let mut lp = LogicPath::new(); + let mut vars = Vec::new(); + for (i, node) in path.iter().copied().rev().enumerate() { + let gate = node.gate; + if i == 0 { + lp.append_sized_gate(primitive_gate_model(gate)); + } else { + let var = lp.create_variable_with_initial(2.); + let model = primitive_gate_model(gate); + let branching = path[path.len() - 1 - i].n_branching - 1; + if branching > 0 { + let mult = branching as f64 * model.cin; + assert!(mult >= 0.0, "mult must be larger than zero, got {mult}"); + lp.append_variable_capacitor(mult, var); + } + + lp.append_unsized_gate(model, var); + println!("append unsized {gate:?}"); + vars.push(var); + } + } + + lp.append_capacitor(path[0].n_branching as f64 * *end); + + lp.size_with_opts(OptimizerOpts { + lr: 1e11, + lr_decay: 0.999995, + max_iter: 10_000_000, + }); + + let mut cnode: Option<&mut SizedGateTreeNode> = None; + let mut tree = None; + + let mut values = vars + .iter() + .rev() + .map(|v| { + let v = lp.value(*v); + assert!(v >= 0.2, "gate scale must be at least 0.2, got {v:.3}"); + v + }) + .collect::>(); + values.push(1.); + println!("values = {values:?}"); + let mut values = values.into_iter(); + + for &node in path { + let gate = match node.gate { + PrimitiveGateType::Inv => crate::blocks::decoder::scale( + crate::blocks::decoder::INV_PARAMS, + values.next().unwrap(), + ), + PrimitiveGateType::Nand2 => crate::blocks::decoder::scale( + crate::blocks::decoder::NAND2_PARAMS, + values.next().unwrap(), + ), + PrimitiveGateType::Nand3 => crate::blocks::decoder::scale( + crate::blocks::decoder::NAND3_PARAMS, + values.next().unwrap(), + ), + PrimitiveGateType::Nor2 => crate::blocks::decoder::scale( + crate::blocks::decoder::NOR2_PARAMS, + values.next().unwrap(), + ), + }; + + let n = SizedGateTreeNode { + gate, + gate_type: node.gate, + id: node.id, + n_branching: node.n_branching, + children: vec![], + }; + + if let Some(parent) = cnode { + parent.children.push(n); + cnode = Some(&mut parent.children[0]) + } else { + tree = Some(n); + cnode = Some(tree.as_mut().unwrap()); + } + } + + tree.unwrap() +} + +impl Tree for GateTreeNode { + fn children(&self) -> &[Self] { + &self.children + } + + fn children_mut(&mut self) -> &mut [Self] { + &mut self.children + } + + fn add_right_child(&mut self, child: Self) { + self.children.push(child); + } +} + +impl Tree for SizedGateTreeNode { + fn children(&self) -> &[Self] { + &self.children + } + + fn children_mut(&mut self) -> &mut [Self] { + &mut self.children + } + + fn add_right_child(&mut self, child: Self) { + self.children.push(child); + } +} + +impl ValueTree for SizedGateTreeNode { + fn value_for_child(&self, idx: usize) -> f64 { + let model = primitive_gate_model(self.gate_type); + model.cin * self.gate.nwidth as f64 / (primitive_gate_params(self.gate_type).nwidth as f64) + } +} diff --git a/src/blocks/sram/layout.rs b/src/blocks/sram/layout.rs index 20f11cb3..15c4aa86 100644 --- a/src/blocks/sram/layout.rs +++ b/src/blocks/sram/layout.rs @@ -102,7 +102,7 @@ impl SramInner { mux_ratio: self.params.mux_ratio, })?; let mut cols = ctx.instantiate::(&col_params)?; - let tree = DecoderTree::with_scale_and_skew(self.params.row_bits, 2, false); + let tree = DecoderTree::new(self.params.row_bits, 128e-15); // TODO let decoder_params = DecoderStageParams { gate: tree.root.gate, num: tree.root.num, @@ -129,7 +129,7 @@ impl SramInner { let p1_bits = tree.root.children[0].num.ilog2() as usize; let p2_bits = tree.root.children[1].num.ilog2() as usize; - let col_tree = DecoderTree::for_columns(self.params.col_select_bits, 1); + let col_tree = DecoderTree::new(self.params.col_select_bits, 128e-15); // TODO let col_decoder_params = DecoderParams { tree: col_tree.clone(), }; diff --git a/src/blocks/sram/mod.rs b/src/blocks/sram/mod.rs index 4bac9d09..5baab10d 100644 --- a/src/blocks/sram/mod.rs +++ b/src/blocks/sram/mod.rs @@ -19,6 +19,9 @@ pub mod schematic; pub mod testbench; pub mod verilog; +pub const WORDLINE_CAP_PER_CELL: f64 = 0.00000000000001472468276676486 / 12.; +pub const READ_MUX_INPUT_CAP: f64 = WORDLINE_CAP_PER_CELL * 4.; // TODO + pub struct SramInner { params: SramParams, } diff --git a/src/blocks/sram/schematic.rs b/src/blocks/sram/schematic.rs index 05448799..393b3599 100644 --- a/src/blocks/sram/schematic.rs +++ b/src/blocks/sram/schematic.rs @@ -17,7 +17,7 @@ use crate::blocks::precharge::{Precharge, PrechargeParams}; use crate::blocks::rmux::ReadMuxParams; use crate::blocks::wmux::WriteMuxSizing; -use super::{ControlMode, SramInner}; +use super::{ControlMode, SramInner, READ_MUX_INPUT_CAP, WORDLINE_CAP_PER_CELL}; impl SramInner { pub(crate) fn schematic(&self, ctx: &mut SchematicCtx) -> Result<()> { @@ -83,7 +83,9 @@ impl SramInner { "sense_en", ]); - let tree = DecoderTree::with_scale_and_skew(self.params.row_bits, 2, false); + let wl_cap = (self.params.cols + 4) as f64 * WORDLINE_CAP_PER_CELL; + println!("wl_cap = {:.3}pF", wl_cap * 1e12); + let tree = DecoderTree::new(self.params.row_bits, wl_cap); ctx.instantiate::(&AddrGateParams { gate: tree.root.gate, @@ -115,7 +117,11 @@ impl SramInner { .named("decoder") .add_to(ctx); - let col_tree = DecoderTree::for_columns(self.params.col_select_bits, 1); + // TODO add wmux driver input capacitance + let col_tree = DecoderTree::new( + self.params.col_select_bits, + READ_MUX_INPUT_CAP * (self.params.cols / self.params.mux_ratio) as f64, + ); let col_decoder_params = DecoderParams { tree: col_tree.clone(), }; diff --git a/src/lib.rs b/src/lib.rs index 5c739416..d77af5d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use crate::verification::calibre::SKY130_LAYERPROPS_PATH; pub use anyhow::{anyhow, Result}; use lazy_static::lazy_static; #[cfg(not(feature = "commercial"))] @@ -89,6 +90,7 @@ pub fn setup_ctx() -> SubstrateCtx { .runset_file(PathBuf::from( crate::verification::calibre::SKY130_DRC_RUNSET_PATH, )) + .layerprops(PathBuf::from(SKY130_LAYERPROPS_PATH)) .build() .unwrap(), ) diff --git a/src/verification/calibre.rs b/src/verification/calibre.rs index c90309e8..17a05df3 100644 --- a/src/verification/calibre.rs +++ b/src/verification/calibre.rs @@ -1,6 +1,7 @@ pub(crate) const SKY130_DRC_RULES_PATH: &str = "/tools/commercial/skywater/swtech130/skywater-src-nda/s8/V2.0.1/DRC/Calibre/s8_drcRules"; pub(crate) const SKY130_DRC_RUNSET_PATH: &str = "/tools/B/rahulkumar/sky130/priv/drc/runset"; +pub(crate) const SKY130_LAYERPROPS_PATH: &str = "/tools/C/ethanwu10/sky130/nda/sky130.layerprops"; pub(crate) const SKY130_LVS_RULES_PATH: &str = "/tools/commercial/skywater/swtech130/skywater-src-nda/s8/V2.0.1/LVS/Calibre/lvs_s8_opts";