use super::io_layout_builder::IoLayout;
use crate::bounding_box::BoundingBox;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Face {
Down,
Up,
North,
South,
West,
East,
}
impl Face {
pub fn parse(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"down" => Some(Face::Down),
"up" => Some(Face::Up),
"north" => Some(Face::North),
"south" => Some(Face::South),
"west" => Some(Face::West),
"east" => Some(Face::East),
_ => None,
}
}
pub fn normal(&self) -> (i32, i32, i32) {
match self {
Face::Down => (0, -1, 0),
Face::Up => (0, 1, 0),
Face::North => (0, 0, -1),
Face::South => (0, 0, 1),
Face::West => (-1, 0, 0),
Face::East => (1, 0, 0),
}
}
pub fn opposite(&self) -> Face {
match self {
Face::Down => Face::Up,
Face::Up => Face::Down,
Face::North => Face::South,
Face::South => Face::North,
Face::West => Face::East,
Face::East => Face::West,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PortDirection {
Input,
Output,
}
impl PortDirection {
pub fn parse(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"input" | "in" => Some(PortDirection::Input),
"output" | "out" => Some(PortDirection::Output),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EdgeWindow {
pub face: Face,
pub boxes: Vec<BoundingBox>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct EdgeContract {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub windows: Vec<EdgeWindow>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PortPairDelay {
pub from: String,
pub to: String,
pub delay_rt: u32,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum InitialState {
#[default]
Unspecified,
Baked {
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
port_values: BTreeMap<String, u64>,
},
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct PhysicalContract {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub keepouts: Vec<BoundingBox>,
#[serde(default)]
pub edge_contract: EdgeContract,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub delays_rt: Vec<PortPairDelay>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub drive_strength: BTreeMap<String, u8>,
#[serde(default)]
pub paste_safe: bool,
#[serde(default)]
pub initial_state: InitialState,
}
impl PhysicalContract {
pub fn delay_rt(&self, from: &str, to: &str) -> Option<u32> {
self.delays_rt
.iter()
.find(|d| d.from == from && d.to == to)
.map(|d| d.delay_rt)
}
pub fn set_delay_rt(&mut self, from: impl Into<String>, to: impl Into<String>, delay_rt: u32) {
let (from, to) = (from.into(), to.into());
if let Some(d) = self
.delays_rt
.iter_mut()
.find(|d| d.from == from && d.to == to)
{
d.delay_rt = delay_rt;
} else {
self.delays_rt.push(PortPairDelay { from, to, delay_rt });
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CellContract {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
pub io: IoLayout,
#[serde(default)]
pub physical: PhysicalContract,
}
impl CellContract {
pub fn new(name: impl Into<String>, io: IoLayout) -> Self {
Self {
name: name.into(),
version: None,
io,
physical: PhysicalContract::default(),
}
}
pub fn to_json(&self) -> Result<String, String> {
serde_json::to_string_pretty(self).map_err(|e| e.to_string())
}
pub fn from_json(json: &str) -> Result<Self, String> {
serde_json::from_str(json).map_err(|e| e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::super::{IoLayoutBuilder, IoType, LayoutFunction};
use super::*;
fn layout() -> IoLayout {
IoLayoutBuilder::new()
.add_input(
"a",
IoType::UnsignedInt { bits: 4 },
LayoutFunction::OneToOne,
vec![(0, 0, 0), (0, 0, 2), (0, 0, 4), (0, 0, 6)],
)
.unwrap()
.add_output(
"sum",
IoType::UnsignedInt { bits: 4 },
LayoutFunction::OneToOne,
vec![(9, 0, 0), (9, 0, 2), (9, 0, 4), (9, 0, 6)],
)
.unwrap()
.build()
}
#[test]
fn face_normals_and_opposites() {
assert_eq!(Face::East.normal(), (1, 0, 0));
assert_eq!(Face::East.opposite(), Face::West);
assert_eq!(Face::parse("NORTH"), Some(Face::North));
assert_eq!(Face::parse("bogus"), None);
}
#[test]
fn delay_table_set_and_get() {
let mut pc = PhysicalContract::default();
pc.set_delay_rt("a", "sum", 3);
pc.set_delay_rt("a", "sum", 4); assert_eq!(pc.delay_rt("a", "sum"), Some(4));
assert_eq!(pc.delay_rt("sum", "a"), None);
}
#[test]
fn cell_contract_json_round_trip() {
let mut contract = CellContract::new("full_adder", layout());
contract.version = Some("1.0.0".to_string());
contract.physical.keepouts.push(BoundingBox {
min: (0, 0, 0),
max: (9, 3, 6),
});
contract.physical.edge_contract.windows.push(EdgeWindow {
face: Face::East,
boxes: vec![BoundingBox {
min: (9, 0, 0),
max: (9, 0, 6),
}],
});
contract.physical.set_delay_rt("a", "sum", 2);
contract.physical.drive_strength.insert("sum".into(), 15);
contract.physical.paste_safe = true;
contract.physical.initial_state = InitialState::Baked {
port_values: [("sum".to_string(), 0u64)].into_iter().collect(),
};
let json = contract.to_json().unwrap();
let back = CellContract::from_json(&json).unwrap();
assert_eq!(back, contract);
assert!(back.io.get_input("a").is_some());
assert_eq!(back.physical.delay_rt("a", "sum"), Some(2));
}
#[test]
fn default_physical_contract_is_minimal_json() {
let pc = PhysicalContract::default();
let json = serde_json::to_string(&pc).unwrap();
assert!(!json.contains("keepouts"));
assert!(!json.contains("delays_rt"));
let back: PhysicalContract = serde_json::from_str(&json).unwrap();
assert_eq!(back, pc);
}
}