use crate::mod_def::dtypes::{EdgeOrientation, Polygon, Range};
use indexmap::IndexMap;
use fixedbitset::FixedBitSet;
use crate::mod_def::{
BOTTOM_EDGE_INDEX, EAST_EDGE_INDEX, LEFT_EDGE_INDEX, NORTH_EDGE_INDEX, RIGHT_EDGE_INDEX,
SOUTH_EDGE_INDEX, TOP_EDGE_INDEX, WEST_EDGE_INDEX,
};
use crate::{Mat3, ModDef};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PinPlacementError {
NotInitialized(&'static str),
EdgeOutOfBounds { edge_index: usize, num_edges: usize },
LayerUnavailable { layer: String },
OutOfBounds {
min_index: i64,
max_index: i64,
num_tracks: usize,
},
OverlapsExistingPin { min_index: i64, max_index: i64 },
OverlapsKeepout { min_index: i64, max_index: i64 },
}
impl fmt::Display for PinPlacementError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PinPlacementError::NotInitialized(what) => {
write!(
f,
"{what} not initialized; call set_shape and set_track_definitions first"
)
}
PinPlacementError::EdgeOutOfBounds {
edge_index,
num_edges,
} => write!(
f,
"edge index {edge_index} is out of bounds ({num_edges} edges available)"
),
PinPlacementError::LayerUnavailable { layer } => {
write!(f, "layer '{layer}' has no tracks on this edge")
}
PinPlacementError::OutOfBounds {
min_index,
max_index,
num_tracks,
} => write!(
f,
"requested track span [{min_index}..={max_index}] is outside available range [0..={}]",
num_tracks.saturating_sub(1)
),
PinPlacementError::OverlapsExistingPin {
min_index,
max_index,
} => write!(
f,
"requested track span [{min_index}..={max_index}] overlaps an existing pin"
),
PinPlacementError::OverlapsKeepout {
min_index,
max_index,
} => write!(
f,
"requested track span [{min_index}..={max_index}] overlaps a keepout region"
),
}
}
}
impl std::error::Error for PinPlacementError {}
#[derive(Clone, PartialEq, Eq)]
pub enum TrackOrientation {
Horizontal,
Vertical,
}
impl TrackOrientation {
pub fn is_compatible_with_edge_orientation(&self, edge_orientation: &EdgeOrientation) -> bool {
matches!(
(self, edge_orientation),
(
TrackOrientation::Horizontal,
EdgeOrientation::North | EdgeOrientation::South
) | (
TrackOrientation::Vertical,
EdgeOrientation::East | EdgeOrientation::West
)
)
}
}
#[derive(Clone)]
pub struct TrackDefinition {
pub(crate) name: String,
pub(crate) offset: i64,
pub(crate) period: i64,
pub(crate) orientation: TrackOrientation,
pub(crate) pin_shape: Option<Polygon>,
pub(crate) keepout_shape: Option<Polygon>,
}
impl TrackDefinition {
pub fn new(
name: impl AsRef<str>,
offset: i64,
period: i64,
orientation: TrackOrientation,
pin_shape: Option<Polygon>,
keepout_shape: Option<Polygon>,
) -> Self {
TrackDefinition {
name: name.as_ref().to_string(),
offset,
period,
orientation,
pin_shape,
keepout_shape,
}
}
pub fn get_pin_shape(&self) -> Option<&Polygon> {
self.pin_shape.as_ref()
}
pub fn set_pin_shape(&mut self, pin_shape: Option<Polygon>) {
self.pin_shape = pin_shape;
}
pub fn get_keepout_shape(&self) -> Option<&Polygon> {
self.keepout_shape.as_ref()
}
pub fn set_keepout_shape(&mut self, keepout_shape: Option<Polygon>) {
self.keepout_shape = keepout_shape;
}
pub fn convert_coord_range_to_index_range(&self, range: &Range) -> Range {
debug_assert!(self.period > 0);
Range {
min: range
.min
.map(|min| (min - self.offset + self.period - 1) / self.period),
max: range.max.map(|max| (max - self.offset) / self.period),
}
}
pub fn convert_index_range_to_coord_range(&self, range: &Range) -> Range {
Range {
min: range.min.map(|min| self.offset + (min * self.period)),
max: range.max.map(|max| self.offset + (max * self.period)),
}
}
pub fn index_to_position(&self, index: i64) -> i64 {
self.offset + (index * self.period)
}
pub fn nearest_track_index(&self, coordinate: i64) -> i64 {
assert!(self.period != 0, "Track period must be non-zero");
let n = coordinate - self.offset;
let p = self.period;
let mut q = n / p;
let r = n % p;
if 2 * r.abs() >= p.abs() {
q += if n >= 0 { 1 } else { -1 };
}
q
}
}
#[derive(Clone)]
pub struct TrackDefinitions(pub(crate) IndexMap<String, TrackDefinition>);
impl TrackDefinitions {
pub fn new() -> Self {
Self::default()
}
pub fn add_track(&mut self, track: TrackDefinition) {
self.0.insert(track.name.clone(), track);
}
pub fn get_track(&self, name: &str) -> Option<&TrackDefinition> {
self.0.get(name)
}
pub fn get_track_mut(&mut self, name: &str) -> Option<&mut TrackDefinition> {
self.0.get_mut(name)
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &TrackDefinition)> {
self.0.iter()
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.0.keys()
}
pub fn values(&self) -> impl Iterator<Item = &TrackDefinition> {
self.0.values()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&String, &mut TrackDefinition)> {
self.0.iter_mut()
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut TrackDefinition> {
self.0.values_mut()
}
}
impl Default for TrackDefinitions {
fn default() -> Self {
TrackDefinitions(IndexMap::new())
}
}
#[derive(Clone)]
pub(crate) struct TrackOccupancy {
pub(crate) pin_occupancies: FixedBitSet,
pub(crate) keepout_occupancies: FixedBitSet,
}
impl TrackOccupancy {
pub fn new(num_tracks: usize) -> Self {
TrackOccupancy {
pin_occupancies: FixedBitSet::with_capacity(num_tracks),
keepout_occupancies: FixedBitSet::with_capacity(num_tracks),
}
}
pub fn check_place_pin(&self, min_index: i64, max_index: i64) -> Result<(), PinPlacementError> {
if min_index < 0 || max_index < 0 || (max_index as usize) >= self.pin_occupancies.len() {
return Err(PinPlacementError::OutOfBounds {
min_index,
max_index,
num_tracks: self.pin_occupancies.len(),
});
}
let min_index_usize = min_index as usize;
let max_index_usize = max_index as usize;
let range = min_index_usize..(max_index_usize + 1);
if self.pin_occupancies.contains_any_in_range(range.clone()) {
return Err(PinPlacementError::OverlapsExistingPin {
min_index,
max_index,
});
}
if self.keepout_occupancies.contains_any_in_range(range) {
return Err(PinPlacementError::OverlapsKeepout {
min_index,
max_index,
});
}
Ok(())
}
pub fn check_place_keepout(
&self,
min_index: i64,
max_index: i64,
) -> Result<(), PinPlacementError> {
let clipped_min_index = min_index.max(0);
let clipped_max_index = max_index.min((self.pin_occupancies.len() as i64) - 1);
if clipped_max_index < clipped_min_index {
return Ok(());
}
let range = (clipped_min_index as usize)..((clipped_max_index as usize) + 1);
if self.pin_occupancies.contains_any_in_range(range) {
return Err(PinPlacementError::OverlapsExistingPin {
min_index,
max_index,
});
}
Ok(())
}
pub fn mark_pin(&mut self, min_index: i64, max_index: i64) {
let min_index = min_index as usize;
let max_index = max_index as usize;
self.pin_occupancies
.set_range(min_index..(max_index + 1), true);
}
pub fn mark_keepout(&mut self, min_index: i64, max_index: i64) {
let clipped_min_index = min_index.max(0);
let clipped_max_index = max_index.min((self.pin_occupancies.len() as i64) - 1);
if clipped_max_index < clipped_min_index {
return;
}
self.keepout_occupancies.set_range(
(clipped_min_index as usize)..((clipped_max_index as usize) + 1),
true,
);
}
pub fn place_pin_and_keepout(
&mut self,
pin_min_index: i64,
pin_max_index: i64,
keepout_min_index: i64,
keepout_max_index: i64,
) {
self.mark_pin(pin_min_index, pin_max_index);
self.mark_keepout(keepout_min_index, keepout_max_index);
let pin_range = (pin_min_index as usize)..((pin_max_index as usize) + 1);
self.keepout_occupancies.remove_range(pin_range);
}
pub fn get_available_indices_in_range(
&self,
min_index: usize,
max_index: usize,
) -> FixedBitSet {
let mut retval = self.pin_occupancies.clone();
retval.union_with(&self.keepout_occupancies);
if min_index > 0 {
retval.insert_range(..min_index);
}
if max_index < (retval.len() - 1) {
retval.insert_range((max_index + 1)..);
}
retval.toggle_range(..);
retval
}
}
pub(crate) struct TrackOccupancies(pub(crate) Vec<IndexMap<String, TrackOccupancy>>);
impl TrackOccupancies {
pub fn new(num_edges: usize) -> Self {
let mut occupancies = Vec::with_capacity(num_edges);
for _ in 0..num_edges {
occupancies.push(IndexMap::new());
}
TrackOccupancies(occupancies)
}
pub fn get_occupancy(
&self,
edge_index: usize,
layer: impl AsRef<str>,
) -> Option<&TrackOccupancy> {
self.0
.get(edge_index)
.and_then(|edge_map| edge_map.get(layer.as_ref()))
}
pub fn get_occupancy_mut(
&mut self,
edge_index: usize,
layer: impl AsRef<str>,
) -> Option<&mut TrackOccupancy> {
self.0
.get_mut(edge_index)
.and_then(|edge_map| edge_map.get_mut(layer.as_ref()))
}
}
macro_rules! can_place_pin_on_edge {
($fn_name:ident, $const_name:ident) => {
#[doc = concat!(
"Returns `true` when a pin can be placed on the ",
stringify!($fn_name),
" edge for the requested layer and track index."
)]
pub fn $fn_name(&self, layer: impl AsRef<str>, track_index: usize) -> bool {
self.can_place_pin_on_edge_index($const_name, layer, track_index)
}
};
}
impl ModDef {
pub fn edge_index_to_transform(&self, edge_index: usize) -> Mat3 {
self.get_edge(edge_index)
.unwrap_or_else(|| panic!("Edge index {edge_index} is out of bounds"))
.to_pin_transform()
}
pub fn track_index_to_transform(
&self,
edge_index: usize,
layer: impl AsRef<str>,
track_index: usize,
) -> Mat3 {
let layer_ref = layer.as_ref();
let track = self
.get_track(layer_ref)
.unwrap_or_else(|| panic!("Unknown track layer '{layer_ref}'"));
let edge = self
.get_edge(edge_index)
.unwrap_or_else(|| panic!("Edge index {edge_index} is out of bounds"));
let position = edge.get_coordinate_on_edge(&track, track_index);
let rotation = edge.to_pin_transform();
let translation = Mat3::translate(position.x, position.y);
&translation * &rotation
}
pub(crate) fn track_range_for_polygon(
&self,
edge_index: usize,
layer: impl AsRef<str>,
polygon: &Polygon,
) -> (i64, i64) {
let layer_ref = layer.as_ref();
let track = self
.get_track(layer_ref)
.unwrap_or_else(|| panic!("Unknown track layer '{layer_ref}'"));
let edge = self
.get_edge(edge_index)
.unwrap_or_else(|| panic!("Edge index {edge_index} is out of bounds"));
let edge_range = edge
.get_index_range(&track)
.unwrap_or_else(|| panic!("layer '{layer_ref}' has no tracks on edge {edge_index}"));
let edge_min_track = edge_range.min.expect("edge track range missing min index");
let bbox = polygon.bbox();
let (min_coord, max_coord) = match track.orientation {
TrackOrientation::Horizontal => (bbox.min_y, bbox.max_y),
TrackOrientation::Vertical => (bbox.min_x, bbox.max_x),
};
let absolute_range =
track.convert_coord_range_to_index_range(&Range::new(min_coord, max_coord));
let absolute_min_track = absolute_range
.min
.expect("polygon track range missing min index");
let absolute_max_track = absolute_range
.max
.expect("polygon track range missing max index");
(
absolute_min_track - edge_min_track,
absolute_max_track - edge_min_track,
)
}
can_place_pin_on_edge!(can_place_pin_on_west_edge, WEST_EDGE_INDEX);
can_place_pin_on_edge!(can_place_pin_on_left_edge, LEFT_EDGE_INDEX);
can_place_pin_on_edge!(can_place_pin_on_north_edge, NORTH_EDGE_INDEX);
can_place_pin_on_edge!(can_place_pin_on_top_edge, TOP_EDGE_INDEX);
can_place_pin_on_edge!(can_place_pin_on_east_edge, EAST_EDGE_INDEX);
can_place_pin_on_edge!(can_place_pin_on_right_edge, RIGHT_EDGE_INDEX);
can_place_pin_on_edge!(can_place_pin_on_south_edge, SOUTH_EDGE_INDEX);
can_place_pin_on_edge!(can_place_pin_on_bottom_edge, BOTTOM_EDGE_INDEX);
pub fn can_place_pin_on_edge_index(
&self,
edge_index: usize,
layer: impl AsRef<str>,
track_index: usize,
) -> bool {
let track = self.get_track(layer.as_ref()).unwrap();
self.check_pin_placement_on_edge_index_with_polygon(
edge_index,
layer,
track_index,
track.pin_shape.as_ref(),
track.keepout_shape.as_ref(),
)
.is_ok()
}
pub fn check_pin_placement_on_edge_index(
&self,
edge_index: usize,
layer: impl AsRef<str>,
track_index: usize,
) -> Result<(), PinPlacementError> {
let track = self.get_track(layer.as_ref()).unwrap();
self.check_pin_placement_on_edge_index_with_polygon(
edge_index,
layer,
track_index,
track.get_pin_shape(),
track.get_keepout_shape(),
)
}
pub fn check_pin_placement_on_edge_index_with_polygon(
&self,
edge_index: usize,
layer: impl AsRef<str>,
track_index: usize,
pin_polygon: Option<&Polygon>,
keepout_polygon: Option<&Polygon>,
) -> Result<(), PinPlacementError> {
let core = self.core.read();
let occupancies = core
.track_occupancies
.as_ref()
.ok_or(PinPlacementError::NotInitialized("Track occupancies"))?;
let num_edges = occupancies.0.len();
let edge_map = occupancies
.0
.get(edge_index)
.ok_or(PinPlacementError::EdgeOutOfBounds {
edge_index,
num_edges,
})?;
let layer_ref = layer.as_ref();
let occupancy =
edge_map
.get(layer_ref)
.ok_or_else(|| PinPlacementError::LayerUnavailable {
layer: layer_ref.to_string(),
})?;
let transform = self.track_index_to_transform(edge_index, layer_ref, track_index);
if let Some(pin_polygon) = pin_polygon {
let pin_polygon = pin_polygon.apply_transform(&transform);
let (min_track_index, max_track_index) =
self.track_range_for_polygon(edge_index, layer_ref, &pin_polygon);
occupancy.check_place_pin(min_track_index, max_track_index)?;
}
if let Some(keepout_polygon) = keepout_polygon {
let keepout_polygon = keepout_polygon.apply_transform(&transform);
let (min_track_index, max_track_index) =
self.track_range_for_polygon(edge_index, layer_ref, &keepout_polygon);
occupancy.check_place_keepout(min_track_index, max_track_index)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mod_def::ModDef;
use std::collections::HashMap;
#[test]
fn can_change_keepout_shapes() {
let module = ModDef::new("Top");
module.set_width_height(100, 100);
let mut tracks = TrackDefinitions::new();
tracks.add_track(TrackDefinition::new(
"M1",
0,
10,
TrackOrientation::Horizontal,
None,
Some(Polygon::from_width_height(2, 2)),
));
module.set_track_definitions(tracks);
let mut saved = HashMap::new();
for (layer, track_def) in module.get_track_definitions_mut().unwrap().iter_mut() {
saved.insert(layer.clone(), track_def.get_keepout_shape().cloned());
track_def.set_keepout_shape(None);
}
for track_def in module.get_track_definitions().unwrap().values() {
assert!(track_def.get_keepout_shape().is_none());
}
for (layer, track_def) in module.get_track_definitions_mut().unwrap().iter_mut() {
track_def.set_keepout_shape(saved.remove(layer).unwrap());
}
for track_def in module.get_track_definitions().unwrap().values() {
assert!(track_def.get_keepout_shape().is_some());
}
}
}