use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use super::Component;
use crate::{Error, Result};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PortRef {
pub instance: String,
pub port: String,
}
impl PortRef {
pub fn parse(text: &str) -> Result<PortRef> {
match text.split_once('.') {
Some((instance, port)) if !instance.is_empty() && !port.is_empty() => Ok(PortRef {
instance: instance.into(),
port: port.into(),
}),
_ => Err(NetlistError::InvalidName {
name: text.into(),
reason: "a port is written instance.port".into(),
}
.into()),
}
}
}
impl fmt::Display for PortRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.instance, self.port)
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum NetlistError {
InvalidName {
name: String,
reason: String,
},
InvalidComponent {
instance: String,
reason: String,
},
DuplicateInstance(String),
UnknownInstance(String),
UnknownPort(PortRef),
UnknownParameter {
instance: String,
parameter: String,
},
ValueOutOfRange {
instance: String,
parameter: String,
value: f64,
min: f64,
max: f64,
},
SelfConnection(PortRef),
PortUsedTwice {
port: PortRef,
first: String,
},
DuplicateExternal(String),
Dangling(PortRef),
ModeMismatch {
a: PortRef,
b: PortRef,
},
SizeMismatch {
instance: String,
ports: usize,
size: usize,
},
}
impl fmt::Display for NetlistError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NetlistError::InvalidName { name, reason } => write!(f, "\"{name}\": {reason}"),
NetlistError::InvalidComponent { instance, reason } => {
write!(f, "{instance}'s component: {reason}")
}
NetlistError::DuplicateInstance(name) => write!(f, "two instances named {name}"),
NetlistError::UnknownInstance(name) => write!(f, "no instance named {name}"),
NetlistError::UnknownPort(port) => {
write!(f, "{} has no port {}", port.instance, port.port)
}
NetlistError::UnknownParameter {
instance,
parameter,
} => write!(f, "{instance} has no parameter {parameter}"),
NetlistError::ValueOutOfRange {
instance,
parameter,
value,
min,
max,
} => write!(
f,
"{instance}.{parameter} must be from {min} to {max}, got {value}"
),
NetlistError::SelfConnection(port) => write!(f, "{port} is connected to itself"),
NetlistError::PortUsedTwice { port, first } => {
write!(f, "{port} is used twice: it is already {first}")
}
NetlistError::DuplicateExternal(name) => write!(f, "two external ports named {name}"),
NetlistError::Dangling(port) => write!(f, "{port} is neither connected nor exposed"),
NetlistError::ModeMismatch { a, b } => {
write!(f, "{a} and {b} carry different modes")
}
NetlistError::SizeMismatch {
instance,
ports,
size,
} => write!(
f,
"{instance} has {ports} ports but returned a {size} x {size} S-matrix"
),
}
}
}
impl From<NetlistError> for Error {
fn from(e: NetlistError) -> Error {
Error::Netlist(e)
}
}
#[derive(Clone, Debug)]
pub struct Instance {
pub name: String,
pub component: Arc<dyn Component>,
pub values: Vec<f64>,
}
impl Instance {
pub fn port(&self, port: &str) -> Option<usize> {
self.component.ports().iter().position(|p| p.name == port)
}
}
#[derive(Clone, Debug, Default)]
pub struct Netlist {
instances: Vec<Instance>,
by_name: HashMap<String, usize>,
connections: Vec<(PortRef, PortRef)>,
external: Vec<(String, PortRef)>,
}
fn check_name(name: &str) -> std::result::Result<(), NetlistError> {
if name.is_empty() || name.contains('.') || name.chars().any(char::is_whitespace) {
return Err(NetlistError::InvalidName {
name: name.into(),
reason: "a name must be non-empty, without dots or whitespace".into(),
});
}
Ok(())
}
fn check_component(instance: &str, c: &dyn Component) -> std::result::Result<(), NetlistError> {
let invalid = |reason: String| NetlistError::InvalidComponent {
instance: instance.into(),
reason,
};
let ports = c.ports();
for (i, p) in ports.iter().enumerate() {
check_name(&p.name).map_err(|e| invalid(format!("port {e}")))?;
if ports[..i].iter().any(|q| q.name == p.name) {
return Err(invalid(format!("two ports named {}", p.name)));
}
}
let parameters = c.parameters();
for (i, p) in parameters.iter().enumerate() {
if p.name.is_empty() || p.name.chars().any(char::is_whitespace) {
return Err(invalid(format!(
"a parameter's name must be non-empty, without whitespace: \"{}\"",
p.name
)));
}
if parameters[..i].iter().any(|q| q.name == p.name) {
return Err(invalid(format!("two parameters named {}", p.name)));
}
if !p.allows(p.default) {
return Err(invalid(format!(
"{}'s default {} isn't within its range, {} to {}",
p.name, p.default, p.min, p.max
)));
}
}
Ok(())
}
fn modes_match(a: &super::Port, b: &super::Port) -> bool {
match (&a.mode, &b.mode) {
(Some(m), Some(n)) => m.polarization == n.polarization && m.order == n.order,
_ => true,
}
}
impl Netlist {
pub fn new() -> Netlist {
Netlist::default()
}
pub fn add(&mut self, name: &str, component: Arc<dyn Component>) -> Result<()> {
check_name(name)?;
if self.by_name.contains_key(name) {
return Err(NetlistError::DuplicateInstance(name.into()).into());
}
check_component(name, component.as_ref())?;
let values = component.defaults();
self.by_name.insert(name.into(), self.instances.len());
self.instances.push(Instance {
name: name.into(),
component,
values,
});
Ok(())
}
pub fn set(&mut self, instance: &str, parameter: &str, value: f64) -> Result<()> {
let i = self.index(instance)?;
let inst = &mut self.instances[i];
let Some(k) = inst
.component
.parameters()
.iter()
.position(|p| p.name == parameter)
else {
return Err(NetlistError::UnknownParameter {
instance: instance.into(),
parameter: parameter.into(),
}
.into());
};
let p = &inst.component.parameters()[k];
if !p.allows(value) {
return Err(NetlistError::ValueOutOfRange {
instance: instance.into(),
parameter: parameter.into(),
value,
min: p.min,
max: p.max,
}
.into());
}
inst.values[k] = value;
Ok(())
}
pub fn connect(&mut self, a: &str, b: &str) -> Result<()> {
let (a, b) = (PortRef::parse(a)?, PortRef::parse(b)?);
let pa = self.resolve(&a)?;
let pb = self.resolve(&b)?;
if a == b {
return Err(NetlistError::SelfConnection(a).into());
}
self.unused(&a)?;
self.unused(&b)?;
if !modes_match(self.port_of(pa), self.port_of(pb)) {
return Err(NetlistError::ModeMismatch { a, b }.into());
}
self.connections.push((a, b));
Ok(())
}
pub fn expose(&mut self, name: &str, port: &str) -> Result<()> {
check_name(name)?;
let p = PortRef::parse(port)?;
if self.external.iter().any(|(n, _)| n == name) {
return Err(NetlistError::DuplicateExternal(name.into()).into());
}
self.resolve(&p)?;
self.unused(&p)?;
self.external.push((name.into(), p));
Ok(())
}
pub fn instances(&self) -> &[Instance] {
&self.instances
}
pub fn instance(&self, name: &str) -> Option<&Instance> {
self.by_name.get(name).map(|&i| &self.instances[i])
}
pub fn connections(&self) -> &[(PortRef, PortRef)] {
&self.connections
}
pub fn external(&self) -> &[(String, PortRef)] {
&self.external
}
pub fn problems(&self) -> Vec<NetlistError> {
let mut used: HashMap<&PortRef, ()> = HashMap::new();
for (a, b) in &self.connections {
used.insert(a, ());
used.insert(b, ());
}
for (_, p) in &self.external {
used.insert(p, ());
}
let mut problems = Vec::new();
for inst in &self.instances {
for port in inst.component.ports() {
let r = PortRef {
instance: inst.name.clone(),
port: port.name.clone(),
};
if !used.contains_key(&r) {
problems.push(NetlistError::Dangling(r));
}
}
}
problems
}
pub fn validate(&self) -> Result<()> {
match self.problems().into_iter().next() {
Some(p) => Err(p.into()),
None => Ok(()),
}
}
pub(super) fn index(&self, name: &str) -> std::result::Result<usize, NetlistError> {
self.by_name
.get(name)
.copied()
.ok_or_else(|| NetlistError::UnknownInstance(name.into()))
}
pub(super) fn resolve(&self, r: &PortRef) -> std::result::Result<(usize, usize), NetlistError> {
let i = self.index(&r.instance)?;
let p = self.instances[i]
.port(&r.port)
.ok_or_else(|| NetlistError::UnknownPort(r.clone()))?;
Ok((i, p))
}
fn port_of(&self, (i, p): (usize, usize)) -> &super::Port {
&self.instances[i].component.ports()[p]
}
fn unused(&self, r: &PortRef) -> std::result::Result<(), NetlistError> {
let used = |first: String| NetlistError::PortUsedTwice {
port: r.clone(),
first,
};
for (a, b) in &self.connections {
if a == r {
return Err(used(format!("connected to {b}")));
}
if b == r {
return Err(used(format!("connected to {a}")));
}
}
if let Some((name, _)) = self.external.iter().find(|(_, p)| p == r) {
return Err(used(format!("exposed as {name}")));
}
Ok(())
}
}