use fnv::FnvHashMap;
use libreda_logic::network::Logic3;
use num_traits::Zero;
use smallvec::{smallvec, SmallVec};
use uom::si::f64::{Capacitance, Time};
use crate::liberty_library::{LibertyTimingLibrary, Pin};
use crate::traits::timing_library;
use crate::traits::timing_library::TimingLibrary;
use crate::traits::*;
use crate::StaMode;
use libreda_db::prelude::NetlistBase;
use std::borrow::Borrow;
#[derive(Debug)]
pub struct NDLMCellModel<'a, N: NetlistBase> {
sta_mode: StaMode,
delay_model_type: DelayModelType,
library: &'a LibertyTimingLibrary<'a>,
pin_capacitances: FnvHashMap<N::PinId, NDLMOutputLoad>,
pin_data: FnvHashMap<N::PinId, &'a Pin>,
ordered_pins: FnvHashMap<N::CellId, Vec<N::PinId>>,
warn_negative_slew: std::sync::Once,
warn_negative_delay: std::sync::Once,
}
#[derive(Copy, Debug, Clone, PartialEq)]
pub enum DelayModelType {
Constant(Time),
NDLM,
}
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
pub enum EdgePolarity {
Rise,
Fall,
Unknown,
}
impl EdgePolarity {
pub fn complement(&self) -> EdgePolarity {
match self {
EdgePolarity::Rise => EdgePolarity::Fall,
EdgePolarity::Fall => EdgePolarity::Rise,
EdgePolarity::Unknown => EdgePolarity::Unknown,
}
}
}
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
pub enum MinMax {
Min = 0,
Max = 1,
}
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
pub enum RiseFall {
Rise = 0,
Fall = 2,
}
impl Into<timing_library::EdgePolarity> for RiseFall {
fn into(self) -> timing_library::EdgePolarity {
match self {
Self::Rise => timing_library::EdgePolarity::Rise,
Self::Fall => timing_library::EdgePolarity::Fall,
}
}
}
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct NDLMSignal {
edge_polarity: EdgePolarity,
slew: [Time; 4],
delay: [Time; 4],
logic_value: Logic3,
}
impl NDLMSignal {
pub fn new(slew_rate: Time, arrival_time: Time) -> Self {
Self {
edge_polarity: EdgePolarity::Unknown,
logic_value: Logic3::X,
slew: [slew_rate; 4],
delay: [arrival_time; 4],
}
}
pub fn set_polarity(&mut self, edge_polarity: EdgePolarity) {
self.edge_polarity = edge_polarity;
}
pub fn with_polarity(mut self, edge_polarity: EdgePolarity) -> Self {
self.set_polarity(edge_polarity);
self
}
pub fn slew(&self, minmax: MinMax, edge: RiseFall) -> Time {
self.slew[minmax as usize + edge as usize]
}
fn max_slew(&self) -> Time {
self.slew(MinMax::Max, RiseFall::Rise)
.max(self.slew(MinMax::Max, RiseFall::Fall))
}
fn min_slew(&self) -> Time {
self.slew(MinMax::Min, RiseFall::Rise)
.min(self.slew(MinMax::Min, RiseFall::Fall))
}
fn max_delay(&self) -> Time {
self.delay(MinMax::Max, RiseFall::Rise)
.max(self.delay(MinMax::Max, RiseFall::Fall))
}
fn min_delay(&self) -> Time {
self.delay(MinMax::Min, RiseFall::Rise)
.min(self.delay(MinMax::Min, RiseFall::Fall))
}
pub fn delay(&self, minmax: MinMax, edge: RiseFall) -> Time {
self.delay[minmax as usize + edge as usize]
}
fn slew_mut(&mut self, minmax: MinMax, edge: RiseFall) -> &mut Time {
&mut self.slew[minmax as usize + edge as usize]
}
fn delay_mut(&mut self, minmax: MinMax, edge: RiseFall) -> &mut Time {
&mut self.delay[minmax as usize + edge as usize]
}
pub fn set_slew_rates(&mut self, slew_rate: Time) {
assert!(slew_rate >= Time::zero(), "slew rate must be positive");
self.slew = [slew_rate; 4];
}
pub fn set_arrival_times(&mut self, arrival_time: Time) {
self.delay = [arrival_time; 4];
}
pub fn set_slew_rate(&mut self, corner: MinMax, edge: RiseFall, slew_rate: Time) {
*self.slew_mut(corner, edge) = slew_rate;
}
pub fn set_arrival_time(&mut self, corner: MinMax, edge: RiseFall, arrival_time: Time) {
*self.delay_mut(corner, edge) = arrival_time;
}
fn acc_arrival_time(&mut self, edge: RiseFall, arrival_time: Time) {
self.set_arrival_time(
MinMax::Min,
edge,
arrival_time.min(self.delay(MinMax::Min, edge)),
);
self.set_arrival_time(
MinMax::Max,
edge,
arrival_time.max(self.delay(MinMax::Max, edge)),
);
}
fn acc_slew(&mut self, edge: RiseFall, slew: Time) {
self.set_slew_rate(MinMax::Min, edge, slew.min(self.slew(MinMax::Min, edge)));
self.set_slew_rate(MinMax::Max, edge, slew.max(self.slew(MinMax::Max, edge)));
}
pub fn set_logic_value(mut self, value: Logic3) {
self.logic_value = value;
}
pub fn with_slew_rates(mut self, slew_rate: Time) -> Self {
self.set_slew_rates(slew_rate);
self
}
pub fn with_arrival_times(mut self, arrival_time: Time) -> Self {
self.set_arrival_times(arrival_time);
self
}
pub fn with_slew_rate(mut self, corner: MinMax, edge: RiseFall, slew_rate: Time) -> Self {
self.set_slew_rate(corner, edge, slew_rate);
self
}
pub fn with_arrival_time(mut self, corner: MinMax, edge: RiseFall, arrival_time: Time) -> Self {
self.set_arrival_time(corner, edge, arrival_time);
self
}
pub fn with_logic_value(mut self, value: Logic3) -> Self {
self.set_logic_value(value);
self
}
}
impl Signal for NDLMSignal {
type LogicValue = Logic3;
fn logic_value(&self) -> Self::LogicValue {
self.logic_value
}
}
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct NDLMDelay {
delay: [Time; 4],
}
impl NDLMDelay {
fn get(&self, minmax: MinMax, edge: RiseFall) -> Time {
self.delay[minmax as usize + edge as usize]
}
}
impl Zero for NDLMDelay {
fn zero() -> Self {
Self {
delay: [Zero::zero(); 4],
}
}
fn is_zero(&self) -> bool {
self.delay.iter().all(|d| d.is_zero())
}
}
impl std::ops::Add for NDLMDelay {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
delay: std::array::from_fn(|i| self.delay[i] + rhs.delay[i]),
}
}
}
#[derive(Copy, Debug, Clone)]
pub struct NDLMRequiredSignal {
earliest_arrival_time: Option<Time>,
latest_arrival_time: Option<Time>,
}
#[derive(Copy, Debug, Clone, PartialEq, Default)]
pub struct NDLMSlack {
hold_slack: Option<Time>,
setup_slack: Option<Time>,
}
#[derive(Copy, Clone, Debug, Default)]
pub struct NDLMOutputLoad {
load_capacitance: Capacitance,
}
impl Zero for NDLMOutputLoad {
fn zero() -> Self {
Self {
load_capacitance: Zero::zero(),
}
}
fn is_zero(&self) -> bool {
self.load_capacitance.is_zero()
}
}
impl std::ops::Add for NDLMOutputLoad {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
load_capacitance: self.load_capacitance + rhs.load_capacitance,
}
}
}
#[derive(Copy, Debug, Clone)]
pub struct NDLMConstraint {
setup_time: Option<Time>,
hold_time: Option<Time>,
}
impl<'a, N: NetlistBase> NDLMCellModel<'a, N> {
pub fn new(library: &'a LibertyTimingLibrary, netlist: &N) -> Self {
let ordered_pins = netlist
.each_cell()
.map(|c| {
let pins = netlist.each_pin_vec(&c);
(c, pins)
})
.collect();
let mut model = Self {
sta_mode: StaMode::Early,
library,
pin_capacitances: Default::default(),
pin_data: Default::default(),
delay_model_type: DelayModelType::NDLM,
ordered_pins,
warn_negative_slew: std::sync::Once::new(),
warn_negative_delay: std::sync::Once::new(),
};
model.init(netlist);
model
}
pub fn set_delay_model_type(&mut self, model_type: DelayModelType) {
self.delay_model_type = model_type
}
fn init(&mut self, netlist: &N) {
self.init_capacitance_table(netlist)
}
fn init_capacitance_table(&mut self, netlist: &N) {
for cell_id in netlist.each_cell() {
let cell_name = netlist.cell_name(&cell_id);
for pin_id in netlist.each_pin(&cell_id) {
let pin_name = netlist.pin_name(&pin_id);
let pin_data = self.library.get_pin(cell_name.borrow(), pin_name.borrow());
if let Some(pin_data) = pin_data {
self.pin_capacitances.insert(
pin_id.clone(),
NDLMOutputLoad {
load_capacitance: pin_data.capacitance,
},
);
self.pin_data.insert(pin_id, pin_data);
}
}
}
}
}
impl<'a, N: NetlistBase> LoadBase for NDLMCellModel<'a, N> {
type Load = NDLMOutputLoad;
fn sum_loads(&self, load1: &Self::Load, load2: &Self::Load) -> Self::Load {
*load1 + *load2
}
}
impl<'a, N: NetlistBase> TimingBase for NDLMCellModel<'a, N> {
type Signal = NDLMSignal;
type LogicValue = Logic3;
}
impl<'a, N: NetlistBase> DelayBase for NDLMCellModel<'a, N> {
type Delay = NDLMDelay;
fn summarize_delays(&self, a: &Self::Signal, b: &Self::Signal) -> Self::Signal {
let reduction_function = match self.sta_mode {
StaMode::Early => Time::min,
StaMode::Late => Time::max,
};
let logic_value = if a.logic_value == b.logic_value {
a.logic_value
} else {
Default::default()
};
let reduction_fns = [Time::min, Time::min, Time::max, Time::max];
let delay = std::array::from_fn(|i| (reduction_fns[i])(a.delay[i], b.delay[i]));
let slew = std::array::from_fn(|i| (reduction_fns[i])(a.slew[i], b.slew[i]));
NDLMSignal {
edge_polarity: if a.edge_polarity == b.edge_polarity {
a.edge_polarity
} else {
EdgePolarity::Unknown
},
delay,
slew,
logic_value,
}
}
fn get_delay(&self, from: &Self::Signal, to: &Self::Signal) -> Self::Delay {
NDLMDelay {
delay: std::array::from_fn(|i| to.delay[i] - from.delay[i]),
}
}
}
impl<'a, N: NetlistBase> CellModel<N> for NDLMCellModel<'a, N> {
fn ordered_pins(&self, cell: &N::CellId) -> Vec<N::PinId> {
self.ordered_pins
.get(cell)
.expect("pin ordering not cached for this cell")
.clone()
}
}
impl<'a, N: NetlistBase> CellLoadModel<N> for NDLMCellModel<'a, N> {
fn input_pin_load(
&self,
input_pin: &N::PinId,
_other_inputs: &impl Fn(&N::PinId) -> Option<Self::LogicValue>,
) -> Self::Load {
self.pin_capacitances
.get(input_pin)
.cloned()
.unwrap_or(self.zero())
}
fn zero(&self) -> Self::Load {
Default::default()
}
}
fn convert_edge_polarity(
edge_polarity: EdgePolarity,
) -> SmallVec<[timing_library::EdgePolarity; 2]> {
match edge_polarity {
EdgePolarity::Rise => smallvec![timing_library::EdgePolarity::Rise],
EdgePolarity::Fall => smallvec![timing_library::EdgePolarity::Fall],
EdgePolarity::Unknown => smallvec![
timing_library::EdgePolarity::Rise,
timing_library::EdgePolarity::Fall,
],
}
}
impl<'a, N: NetlistBase> CellDelayModel<N> for NDLMCellModel<'a, N> {
fn cell_output(
&self,
netlist: &N,
input_pin: &N::PinId,
input_signal: &Self::Signal,
output_pin: &N::PinId,
output_load: &Self::Load,
_other_inputs: &impl Fn(&N::PinId) -> Option<Self::LogicValue>,
) -> Option<Self::Signal> {
debug_assert!(
netlist.parent_cell_of_pin(input_pin) == netlist.parent_cell_of_pin(output_pin),
"Pins must live in same cell."
);
let cell = netlist.parent_cell_of_pin(input_pin);
let cell_name = netlist.cell_name(&cell);
let input_pin_name = netlist.pin_name(input_pin);
let output_pin_name = netlist.pin_name(output_pin);
let mut slew_cache: SmallVec<[_; 4]> = Default::default();
let mut delay_cache: SmallVec<[_; 4]> = Default::default();
let slew = |(minmax, edge_polarity): (MinMax, RiseFall)| -> Option<Time> {
let input_slew = input_signal.slew(minmax, edge_polarity);
let cached = slew_cache
.iter()
.find(|(p, s, _)| p == &edge_polarity && s == &input_slew)
.map(|(_, _, s)| *s);
if let Some(slew) = cached {
return slew;
}
let slew = self
.library
.get_slew(
edge_polarity.into(),
cell_name.borrow(),
output_pin_name.borrow(),
input_pin_name.borrow(),
input_slew,
output_load.load_capacitance,
)
.map(|s| {
if s < Time::zero() {
self.warn_negative_slew.call_once(|| {
log::warn!("Negative slewrate. Check the cell model.");
});
}
s.max(Zero::zero())
});
slew_cache.push((edge_polarity, input_slew, slew));
slew
};
let delay = |(minmax, edge_polarity): (MinMax, RiseFall)| -> Option<Time> {
let input_slew = input_signal.slew(minmax, edge_polarity);
let cached = delay_cache
.iter()
.find(|(p, s, _)| p == &edge_polarity && s == &input_slew)
.map(|(_, _, d)| *d);
if let Some(delay) = cached {
return delay;
}
let delay = self
.library
.get_cell_delay(
edge_polarity.into(),
cell_name.borrow(),
output_pin_name.borrow(),
input_pin_name.borrow(),
input_signal.slew(minmax, edge_polarity),
output_load.load_capacitance,
)
.map(|d| {
if d < Time::zero() {
self.warn_negative_delay.call_once(|| {
log::warn!("Negative delay. Check the cell model.");
});
}
d.max(Zero::zero())
});
delay_cache.push((edge_polarity, input_slew, delay));
delay
};
let logic_value = Logic3::X; let edge_polarity = EdgePolarity::Unknown; let args = [
(MinMax::Min, RiseFall::Rise),
(MinMax::Max, RiseFall::Rise),
(MinMax::Min, RiseFall::Fall),
(MinMax::Max, RiseFall::Fall),
];
let possible_edge_types = convert_edge_polarity(input_signal.edge_polarity);
let delays = args.map(delay);
let slews = args.map(slew);
let is_all_none = delays.iter().all(Option::is_none);
if is_all_none {
return None;
}
let mut s = NDLMSignal::new(Time::zero(), Time::zero());
s.edge_polarity = edge_polarity;
s.logic_value = logic_value;
let mut slew_defined = false;
let mut delay_defined = false;
for i in 0..4 {
if let Some(delay) = delays[i] {
s.set_arrival_times(delay);
delay_defined = true;
}
if let Some(slew) = slews[i] {
s.set_slew_rates(slew);
slew_defined = true;
}
}
if !delay_defined && !slew_defined {
return None;
}
for i in 0..4 {
let (_minmax, risefall) = args[i];
if let Some(delay) = delays[i] {
s.acc_arrival_time(risefall, delay);
}
if let Some(slew) = slews[i] {
s.acc_slew(risefall, slew);
}
}
debug_assert!(s.delay.iter().all(|d| d >= &Time::zero()));
debug_assert!(s.slew.iter().all(|s| s >= &Time::zero()));
dbg!(&s.delay);
dbg!(&s.slew);
debug_assert!(s.max_delay() >= s.min_delay());
debug_assert!(s.max_slew() >= s.min_slew());
Some(s)
}
fn delay_arcs(
&self,
netlist: &N,
cell: &N::CellId,
) -> Box<dyn Iterator<Item = CellDelayArc<N::PinId>> + '_> {
let cell_name = netlist.cell_name(cell);
let cell_name_str: &str = cell_name.borrow();
let delay_arcs: SmallVec<[_; 4]> = self
.library
.cells
.get(cell_name_str)
.expect("Cell not in library.")
.pins
.iter()
.flat_map(move |(output_pin_name, pin)| {
let output_pin = netlist
.pin_by_name(cell, output_pin_name)
.expect("No such input pin.");
pin.delay_arcs.keys().map(move |related_pin_name| {
let related_pin = netlist
.pin_by_name(cell, related_pin_name)
.expect("No such output pin.");
CellDelayArc {
output_pin: output_pin.clone(),
input_pin: related_pin,
}
})
})
.collect();
Box::new(delay_arcs.into_iter())
}
fn delay_arcs_from_pin(
&self,
netlist: &N,
input_pin: &N::PinId,
) -> Box<dyn Iterator<Item = N::PinId> + '_> {
let cell = netlist.parent_cell_of_pin(input_pin);
let cell_name = netlist.cell_name(&cell);
let cell_name_str: &str = cell_name.borrow();
let input_pin_name = netlist.pin_name(input_pin);
let input_pin_name_str: &str = input_pin_name.borrow();
let pin = self
.library
.cells
.get(cell_name_str)
.and_then(|cell| cell.pins.get(input_pin_name_str));
let output_pins: SmallVec<[_; 2]> = pin
.into_iter()
.flat_map(move |pin| pin.delay_arcs.keys())
.map(move |out_pin_name| netlist.pin_by_name(&cell, out_pin_name.as_str()).unwrap())
.collect();
Box::new(output_pins.into_iter())
}
fn delay_arcs_reversed(&self, output_pin: &N) -> Box<dyn Iterator<Item = N::PinId>> {
unimplemented!()
}
}
impl<'a, N: NetlistBase> ConstraintBase for NDLMCellModel<'a, N> {
type Constraint = NDLMConstraint;
type RequiredSignal = NDLMRequiredSignal;
type Slack = NDLMSlack;
fn summarize_constraints(
&self,
s1: &Self::RequiredSignal,
s2: &Self::RequiredSignal,
) -> Self::RequiredSignal {
let rs = [s1, s2];
NDLMRequiredSignal {
earliest_arrival_time: rs
.iter()
.flat_map(|s| s.earliest_arrival_time)
.reduce(Time::min),
latest_arrival_time: rs
.iter()
.flat_map(|s| s.latest_arrival_time)
.reduce(Time::max),
}
}
fn solve_delay_constraint(
&self,
actual_delay: &Self::Delay,
required_output: &Self::RequiredSignal,
_actual_input: &Self::Signal,
) -> Self::RequiredSignal {
use MinMax::*;
use RiseFall::*;
let d_max = actual_delay.get(Max, Rise).max(actual_delay.get(Max, Fall));
let d_min = actual_delay.get(Min, Rise).min(actual_delay.get(Min, Fall));
NDLMRequiredSignal {
earliest_arrival_time: required_output.earliest_arrival_time.map(|t| t - d_min),
latest_arrival_time: required_output.latest_arrival_time.map(|t| t - d_max),
}
}
fn get_slack(
&self,
actual_signal: &Self::Signal,
required_signal: &Self::RequiredSignal,
) -> Self::Slack {
use MinMax::*;
use RiseFall::*;
let earliest_arrival_time = actual_signal
.delay(Min, Rise)
.min(actual_signal.delay(Min, Fall));
let latest_arrival_time = actual_signal
.delay(Max, Rise)
.max(actual_signal.delay(Max, Fall));
let hold_slack = required_signal
.earliest_arrival_time
.map(|r| earliest_arrival_time - r);
let setup_slack = required_signal
.latest_arrival_time
.map(|r| r - latest_arrival_time);
NDLMSlack {
hold_slack,
setup_slack,
}
}
}
impl<'a, N: NetlistBase> CellConstraintModel<N> for NDLMCellModel<'a, N> {
fn get_required_input(
&self,
netlist: &N,
constrained_pin: &N::PinId,
constrained_pin_signal: &Self::Signal,
related_pin: &N::PinId,
related_pin_signal: &Self::Signal,
_other_inputs: &impl Fn(&N::PinId) -> Option<Self::Signal>,
output_loads: &impl Fn(&N::PinId) -> Option<Self::Load>,
) -> Option<Self::RequiredSignal> {
let cell = netlist.parent_cell_of_pin(constrained_pin);
assert!(
{
let cell2 = netlist.parent_cell_of_pin(related_pin);
cell == cell2
},
"Both pins must belong to the same cell."
);
let cell_name = netlist.cell_name(&cell);
let cell_name_str: &str = cell_name.borrow();
let constrained_pin_name = netlist.pin_name(constrained_pin);
let constrained_pin_name_str: &str = constrained_pin_name.borrow();
let related_pin_name = netlist.pin_name(related_pin);
let related_pin_name_str: &str = related_pin_name.borrow();
let output_load = output_loads(constrained_pin).unwrap_or(Zero::zero());
let hold_time = |constrained_edge_polarity: timing_library::EdgePolarity,
related_edge_polarity: timing_library::EdgePolarity|
-> Option<Time> {
let constrained_pin_transition = constrained_pin_signal.max_slew();
let related_pin_transition = related_pin_signal.max_slew();
self.library.get_hold_constraint(
cell_name_str,
constrained_pin_name_str,
related_pin_name_str,
constrained_edge_polarity,
related_edge_polarity,
related_pin_transition,
constrained_pin_transition,
output_load.load_capacitance,
)
};
let setup_time = |constrained_edge_polarity: timing_library::EdgePolarity,
related_edge_polarity: timing_library::EdgePolarity|
-> Option<Time> {
let constrained_pin_transition = constrained_pin_signal.max_slew();
let related_pin_transition = related_pin_signal.max_slew();
self.library.get_setup_constraint(
cell_name_str,
constrained_pin_name_str,
related_pin_name_str,
constrained_edge_polarity,
related_edge_polarity,
related_pin_transition,
constrained_pin_transition,
output_load.load_capacitance,
)
};
let possible_related_edge_types = convert_edge_polarity(related_pin_signal.edge_polarity);
let possible_constrained_edge_types =
convert_edge_polarity(constrained_pin_signal.edge_polarity);
let edge_type_combinations = possible_related_edge_types
.iter()
.flat_map(|&r| possible_constrained_edge_types.iter().map(move |&c| (r, c)));
let max_setup_time = edge_type_combinations
.clone()
.filter_map(|(edge_r, edge_c)| setup_time(edge_c, edge_r))
.reduce(Time::max);
let max_hold_time = edge_type_combinations
.filter_map(|(edge_r, edge_c)| hold_time(edge_c, edge_r))
.reduce(Time::max);
if max_setup_time.is_some() || max_hold_time.is_some() {
use MinMax::*;
use RiseFall::*;
let related_earliest_arrival_time = related_pin_signal
.delay(Min, Rise)
.min(related_pin_signal.delay(Min, Fall));
let related_latest_arrival_time = related_pin_signal
.delay(Max, Rise)
.max(related_pin_signal.delay(Max, Fall));
let earliest_arrival_time =
max_hold_time.map(|t_ho| related_earliest_arrival_time + t_ho);
let latest_arrival_time = max_setup_time.map(|t_su| related_latest_arrival_time - t_su);
Some(NDLMRequiredSignal {
earliest_arrival_time,
latest_arrival_time,
})
} else {
None }
}
fn constraint_arcs(
&self,
netlist: &N,
cell_id: &N::CellId,
) -> Box<dyn Iterator<Item = CellConstraintArc<N::PinId>> + '_> {
let cell_name = netlist.cell_name(cell_id);
let cell_name_str: &str = cell_name.borrow();
let constraint_arcs: Vec<_> = self
.library
.cells
.get(cell_name_str)
.expect("Cell not in library.")
.pins
.iter()
.flat_map(move |(constrained_pin_name, pin)| {
let constrained_pin = netlist
.pin_by_name(cell_id, constrained_pin_name)
.expect("No such output pin.");
let all_constraint_arcs = vec![
&pin.setup_rising,
&pin.setup_falling,
&pin.hold_rising,
&pin.hold_falling,
];
all_constraint_arcs
.into_iter()
.flat_map(|constraint_arc| constraint_arc.iter())
.map(move |(related_pin_name, _)| {
let related_pin = netlist
.pin_by_name(cell_id, related_pin_name)
.expect("No such input pin.");
CellConstraintArc {
related_pin,
constrained_pin: constrained_pin.clone(),
}
})
})
.collect();
Box::new(constraint_arcs.into_iter())
}
}