Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 5 additions & 1 deletion substrate/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();
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) => {
Expand Down
98 changes: 81 additions & 17 deletions substrate/src/layout/cell.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,7 +29,11 @@ use crate::deps::arcstr::ArcStr;
use crate::error::ErrorSource;
use crate::fmt::signal::{format_signal, BusFmt};

pub type BusPort = HashMap<usize, CellPort>;
/// 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<usize, CellPort, FxBuildHasher>;

/// The layout view of a cell.
#[derive(Debug, Default)]
Expand Down Expand Up @@ -268,15 +274,17 @@ where

#[derive(Debug, Default, Clone)]
pub struct PortMap {
ports: HashMap<ArcStr, BusPort>,
ports: IndexMap<ArcStr, BusPort, FxBuildHasher>,
}

impl PortMap {
pub fn new() -> Self {
Self::default()
}
pub fn from_map(ports: HashMap<ArcStr, BusPort>) -> Self {
Self { ports }
pub fn from_map(ports: impl IntoIterator<Item = (ArcStr, BusPort)>) -> Self {
Self {
ports: ports.into_iter().collect(),
}
}
pub fn add_port(&mut self, port: impl Into<CellPort>) -> Result<(), PortError> {
self.add_port_with_strategy(port, PortConflictStrategy::default())
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -427,7 +435,7 @@ pub struct CellPort {
/// The port's identifier.
pub(crate) id: PortId,
/// Shapes, grouped by layer.
pub(crate) shapes: HashMap<LayerKey, Vec<Shape>>,
pub(crate) shapes: IndexMap<LayerKey, Vec<Shape>, FxBuildHasher>,
/// Information on how this port must be electrically connected.
///
/// See [`MustConnect`] for more information.
Expand All @@ -453,7 +461,7 @@ impl Transform for CellPort {
let v = v.iter().map(|s| s.transform(trans)).collect::<Vec<_>>();
(*k, v)
})
.collect::<HashMap<_, _>>();
.collect::<IndexMap<_, _, _>>();

Self {
id: self.id.clone(),
Expand All @@ -466,7 +474,7 @@ impl Transform for CellPort {
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct CellPortBuilder {
id: Option<PortId>,
shapes: HashMap<LayerKey, Vec<Shape>>,
shapes: IndexMap<LayerKey, Vec<Shape>, FxBuildHasher>,
must_connect: MustConnect,
}

Expand Down Expand Up @@ -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<usize, CellPort> = HashMap::from_iter(
let new_bus = BusPort::from_iter(
bus.iter()
.filter_map(|(index, port)| port.trim(bounds).map(|port| (*index, port))),
);
Expand Down Expand Up @@ -987,7 +995,7 @@ impl CellPort {
pub fn new(id: impl Into<PortId>) -> Self {
Self {
id: id.into(),
shapes: HashMap::new(),
shapes: IndexMap::default(),
must_connect: Default::default(),
}
}
Expand All @@ -998,7 +1006,7 @@ impl CellPort {
}

pub fn with_shape(id: impl Into<PortId>, layer: LayerKey, shape: impl Into<Shape>) -> Self {
let mut shapes = HashMap::with_capacity(1);
let mut shapes = IndexMap::default();
shapes.insert(layer, vec![shape.into()]);
Self {
id: id.into(),
Expand All @@ -1012,7 +1020,7 @@ impl CellPort {
layer: LayerKey,
shapes: impl IntoIterator<Item = Shape>,
) -> 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 {
Expand All @@ -1023,7 +1031,7 @@ impl CellPort {
}

pub fn with_element(id: impl Into<PortId>, 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(),
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1449,7 +1457,7 @@ where

impl<'a> Port for &'a CellPort {
type ShapeIter = std::iter::Cloned<std::slice::Iter<'a, Shape>>;
type LayerIter = std::iter::Copied<std::collections::hash_map::Keys<'a, LayerKey, Vec<Shape>>>;
type LayerIter = std::iter::Copied<indexmap::map::Keys<'a, LayerKey, Vec<Shape>>>;

fn id(&self) -> &PortId {
&self.id
Expand Down Expand Up @@ -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<LayerKey, ()> = SlotMap::with_key();
let layers: Vec<LayerKey> = (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<LayerKey>)>;
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<LayerKey>)> = 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<LayerKey>)> = 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);
Expand Down
30 changes: 28 additions & 2 deletions substrate/src/layout/layers/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -167,7 +168,8 @@ impl Layers {
mut base: impl FnMut(&str) -> LayerInfo,
) -> Result<Self, csv::Error> {
let mut reader = csv::Reader::from_reader(csv.as_bytes());
let mut layer_infos: HashMap<String, LayerInfo> = HashMap::new();
// In file order, so layers get the same keys (and key order) in every run.
let mut layer_infos: IndexMap<String, LayerInfo> = IndexMap::new();

for record in reader.deserialize() {
let record: CsvLayerRecord = record?;
Expand Down Expand Up @@ -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:?}");
}
}