diff --git a/Cargo.toml b/Cargo.toml index 4b34b0a..8c6e630 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ num-derive = "0.5" num-traits = "0.2" paste = "1" pathfinding = "4" -indexmap = "2" +indexmap = { version = "2", features = ["serde"] } rustc-hash = "2" proc-macro-crate = "3" proc-macro2 = "1" diff --git a/substrate/src/data.rs b/substrate/src/data.rs index 49db786..a710af2 100644 --- a/substrate/src/data.rs +++ b/substrate/src/data.rs @@ -1166,7 +1166,11 @@ impl SubstrateData { self.emit_module(key, &netlist, &mut out)?; } - for module in self.schematics.external_modules() { + // Emit external modules by name, not in the hash map's order, so the netlist is the + // same from run to run. + let mut external_modules = self.schematics.external_modules().collect::>(); + external_modules.sort_unstable_by(|a, b| a.name().cmp(b.name())); + for module in external_modules { let source = module.source(); match source { RawSource::File(path) => { diff --git a/substrate/src/layout/cell.rs b/substrate/src/layout/cell.rs index 7d5ada3..b09ae9b 100644 --- a/substrate/src/layout/cell.rs +++ b/substrate/src/layout/cell.rs @@ -1,12 +1,14 @@ //! Types related to the creation and instantiation of [`Cell`]s. -use std::collections::hash_map::Entry; use std::collections::HashMap; use std::fmt; use std::hash::Hash; use std::sync::Arc; use derive_builder::Builder; +use indexmap::map::Entry; +use indexmap::IndexMap; +use rustc_hash::FxBuildHasher; use serde::{Deserialize, Serialize}; use slotmap::new_key_type; use state::TypeMap; @@ -27,7 +29,11 @@ use crate::deps::arcstr::ArcStr; use crate::error::ErrorSource; use crate::fmt::signal::{format_signal, BusFmt}; -pub type BusPort = HashMap; +/// The ports of a bus, by index. +/// +/// Ports and their shapes iterate in insertion order, so everything generated from them +/// is independent of the process's hash seeds. +pub type BusPort = IndexMap; /// The layout view of a cell. #[derive(Debug, Default)] @@ -268,15 +274,17 @@ where #[derive(Debug, Default, Clone)] pub struct PortMap { - ports: HashMap, + ports: IndexMap, } impl PortMap { pub fn new() -> Self { Self::default() } - pub fn from_map(ports: HashMap) -> Self { - Self { ports } + pub fn from_map(ports: impl IntoIterator) -> Self { + Self { + ports: ports.into_iter().collect(), + } } pub fn add_port(&mut self, port: impl Into) -> Result<(), PortError> { self.add_port_with_strategy(port, PortConflictStrategy::default()) @@ -308,7 +316,7 @@ impl PortMap { } }, Entry::Vacant(v) => { - v.insert(HashMap::from([(id.index, port)])); + v.insert(BusPort::from_iter([(id.index, port)])); } } Ok(()) @@ -427,7 +435,7 @@ pub struct CellPort { /// The port's identifier. pub(crate) id: PortId, /// Shapes, grouped by layer. - pub(crate) shapes: HashMap>, + pub(crate) shapes: IndexMap, FxBuildHasher>, /// Information on how this port must be electrically connected. /// /// See [`MustConnect`] for more information. @@ -453,7 +461,7 @@ impl Transform for CellPort { let v = v.iter().map(|s| s.transform(trans)).collect::>(); (*k, v) }) - .collect::>(); + .collect::>(); Self { id: self.id.clone(), @@ -466,7 +474,7 @@ impl Transform for CellPort { #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct CellPortBuilder { id: Option, - shapes: HashMap>, + shapes: IndexMap, FxBuildHasher>, must_connect: MustConnect, } @@ -833,9 +841,9 @@ impl Cell { .filter_map(|a| a.trim(bounds)) .collect(); - self.ports = PortMap::from_map(HashMap::from_iter(self.ports.bus_ports().filter_map( + self.ports = PortMap::from_map(Vec::from_iter(self.ports.bus_ports().filter_map( |(k, bus)| { - let new_bus: HashMap = HashMap::from_iter( + let new_bus = BusPort::from_iter( bus.iter() .filter_map(|(index, port)| port.trim(bounds).map(|port| (*index, port))), ); @@ -987,7 +995,7 @@ impl CellPort { pub fn new(id: impl Into) -> Self { Self { id: id.into(), - shapes: HashMap::new(), + shapes: IndexMap::default(), must_connect: Default::default(), } } @@ -998,7 +1006,7 @@ impl CellPort { } pub fn with_shape(id: impl Into, layer: LayerKey, shape: impl Into) -> Self { - let mut shapes = HashMap::with_capacity(1); + let mut shapes = IndexMap::default(); shapes.insert(layer, vec![shape.into()]); Self { id: id.into(), @@ -1012,7 +1020,7 @@ impl CellPort { layer: LayerKey, shapes: impl IntoIterator, ) -> Self { - let mut map = HashMap::with_capacity(1); + let mut map = IndexMap::default(); let entry = map.entry(layer).or_insert_with(Vec::new); entry.extend(shapes); Self { @@ -1023,7 +1031,7 @@ impl CellPort { } pub fn with_element(id: impl Into, elem: Element) -> Self { - let mut shapes = HashMap::with_capacity(1); + let mut shapes = IndexMap::default(); shapes.insert(elem.layer.layer(), vec![elem.into_inner()]); Self { id: id.into(), @@ -1296,7 +1304,7 @@ where ) }) .filter(|(_, v)| !v.is_empty()); - let shapes = HashMap::from_iter(iter); + let shapes = IndexMap::from_iter(iter); if shapes.is_empty() { return None; } @@ -1449,7 +1457,7 @@ where impl<'a> Port for &'a CellPort { type ShapeIter = std::iter::Cloned>; - type LayerIter = std::iter::Copied>>; + type LayerIter = std::iter::Copied>>; fn id(&self) -> &PortId { &self.id @@ -1778,6 +1786,62 @@ mod tests { } } + /// Ports iterate in the order they were first added (by name, then by bus index within + /// a name), and each port's shapes by the order their layers were first added, so + /// layouts generated from them do not depend on hash seeds. + #[test] + fn test_port_iteration_follows_insertion_order() { + let mut layer_slots: SlotMap = SlotMap::with_key(); + let layers: Vec = (0..6).map(|_| layer_slots.insert(())).collect(); + let mut rng = Rng(0x2545_f491_4f6c_dd1d); + + let mut map = PortMap::new(); + // Names, the bus indices of each name, and the layers of each port, in first-seen order. + type Bus = Vec<(usize, Vec)>; + let mut expected: Vec<(ArcStr, Bus)> = Vec::new(); + for _ in 0..200 { + let name = ArcStr::from(format!("p{}", rng.below(20))); + let index = rng.below(8) as usize; + let layer = layers[rng.below(layers.len() as u64) as usize]; + map.add_port_with_strategy( + CellPort::with_shape(PortId::new(name.clone(), index), layer, rng.rect(50)), + PortConflictStrategy::Merge, + ) + .unwrap(); + + let bus = match expected.iter().position(|(n, _)| *n == name) { + Some(i) => &mut expected[i].1, + None => { + expected.push((name, Vec::new())); + &mut expected.last_mut().unwrap().1 + } + }; + let port_layers = match bus.iter().position(|(i, _)| *i == index) { + Some(i) => &mut bus[i].1, + None => { + bus.push((index, Vec::new())); + &mut bus.last_mut().unwrap().1 + } + }; + if !port_layers.contains(&layer) { + port_layers.push(layer); + } + } + + let expected: Vec<(ArcStr, usize, Vec)> = expected + .into_iter() + .flat_map(|(name, bus)| { + bus.into_iter() + .map(move |(index, layers)| (name.clone(), index, layers)) + }) + .collect(); + let actual: Vec<(ArcStr, usize, Vec)> = map + .ports() + .map(|port| (port.id.name(), port.id.index(), port.layers().collect())) + .collect(); + assert_eq!(actual, expected); + } + #[test] fn test_coalesce_rects_indexed_matches_pairwise() { let mut rng = Rng(0x9e37_79b9_7f4a_7c15); diff --git a/substrate/src/layout/layers/mod.rs b/substrate/src/layout/layers/mod.rs index 6db1e37..ac6e175 100644 --- a/substrate/src/layout/layers/mod.rs +++ b/substrate/src/layout/layers/mod.rs @@ -1,13 +1,14 @@ //! Utilities and types for managing layers in a PDK. use std::borrow::Borrow; -use std::collections::hash_map::Entry; use std::collections::HashMap; use std::hash::Hash; use std::str::FromStr; use std::sync::{Arc, RwLock}; use derive_builder::Builder; +use indexmap::map::Entry; +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use slotmap::{new_key_type, SlotMap}; use subgeom::bbox::{Bbox, BoundBox}; @@ -167,7 +168,8 @@ impl Layers { mut base: impl FnMut(&str) -> LayerInfo, ) -> Result { let mut reader = csv::Reader::from_reader(csv.as_bytes()); - let mut layer_infos: HashMap = HashMap::new(); + // In file order, so layers get the same keys (and key order) in every run. + let mut layer_infos: IndexMap = IndexMap::new(); for record in reader.deserialize() { let record: CsvLayerRecord = record?; @@ -536,3 +538,27 @@ impl UserLayer { pub trait LayerBoundBox: BoundBox { fn layer_bbox(&self, layer: LayerKey) -> Bbox; } + +#[cfg(test)] +mod tests { + use super::*; + + /// Layers get keys in the order the CSV lists them. Keys order the shapes of + /// layout ports, so assigning them in a hash map's order made generated layouts + /// differ from run to run. + #[test] + fn test_from_csv_assigns_keys_in_file_order() { + let names = [ + "pwell", "nwell", "diff", "poly", "li1", "met1", "met2", "met3", "met4", "met5", + ]; + let mut csv = String::from("layernum,datatype,name,purpose\n"); + for (i, name) in names.iter().enumerate() { + csv.push_str(&format!("{i},20,{name},drawing\n{i},16,{name},pin\n")); + } + let layers = + Layers::from_csv(&csv, |_| LayerInfoBuilder::default().build().unwrap()).unwrap(); + + let keys = names.map(|name| layers.get_key(name).unwrap()); + assert!(keys.windows(2).all(|w| w[0] < w[1]), "{keys:?}"); + } +}