use std::collections::HashSet;
use fnv::FnvHashMap;
use libreda_logic::logic_value::Logic3;
use num_traits::Zero;
use smallvec::SmallVec;
use uom::si::f64::{Capacitance, Time};
use crate::liberty_library::{LibertyTimingLibrary, Pin};
use crate::traits::cell_logic_model::{LogicModel, Unateness};
use crate::traits::timing_library::TimingLibrary;
use crate::{traits::*, DelayArcArg, RiseFall};
use crate::{ConstraintArcArg, StaMode};
use libreda_db::prelude::NetlistBase;
use libreda_db::traits::NetlistIds;
#[derive(Debug)]
pub struct NDLMCellModel<'a, N: NetlistIds> {
sta_mode: StaMode,
delay_model_type: DelayModelType,
library: &'a LibertyTimingLibrary<N::CellId, N::PinId>,
pin_capacitances: FnvHashMap<N::PinId, NDLMOutputLoad>,
pin_data: FnvHashMap<N::PinId, &'a Pin<N::PinId>>,
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, PartialEq, Eq, Hash)]
pub enum MinMax {
Min = 0,
Max = 1,
}
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct NDLMSignal {
slew: [Time; 2],
delay: [Time; 2],
logic_value: Logic3,
edge_polarity: SignalTransitionType,
}
impl NDLMSignal {
pub fn new(slew_rate: Time, arrival_time: Time) -> Self {
Self {
edge_polarity: SignalTransitionType::Any,
logic_value: Logic3::X,
slew: [slew_rate; 2],
delay: [arrival_time; 2],
}
}
pub fn set_polarity(&mut self, edge_polarity: SignalTransitionType) {
self.edge_polarity = edge_polarity;
}
pub fn with_polarity(mut self, edge_polarity: SignalTransitionType) -> Self {
self.set_polarity(edge_polarity);
self
}
pub fn slew(&self, minmax: MinMax) -> Time {
self.slew[minmax as usize]
}
pub fn delay(&self, minmax: MinMax) -> Time {
self.delay[minmax as usize]
}
fn slew_mut(&mut self, minmax: MinMax) -> &mut Time {
&mut self.slew[minmax as usize]
}
fn delay_mut(&mut self, minmax: MinMax) -> &mut Time {
&mut self.delay[minmax 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; 2];
}
pub fn set_arrival_times(&mut self, arrival_time: Time) {
self.delay = [arrival_time; 2];
}
pub fn set_slew_rate(&mut self, corner: MinMax, slew_rate: Time) {
*self.slew_mut(corner) = slew_rate;
}
pub fn set_arrival_time(&mut self, corner: MinMax, arrival_time: Time) {
*self.delay_mut(corner) = arrival_time;
}
fn summarize_arrival_time(&mut self, arrival_time: Time) {
self.set_arrival_time(MinMax::Min, arrival_time.min(self.delay(MinMax::Min)));
self.set_arrival_time(MinMax::Max, arrival_time.max(self.delay(MinMax::Max)));
}
fn acc_slew(&mut self, slew: Time) {
self.set_slew_rate(MinMax::Min, slew.min(self.slew(MinMax::Min)));
self.set_slew_rate(MinMax::Max, slew.max(self.slew(MinMax::Max)));
}
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, slew_rate: Time) -> Self {
self.set_slew_rate(corner, slew_rate);
self
}
pub fn with_arrival_time(mut self, corner: MinMax, arrival_time: Time) -> Self {
self.set_arrival_time(corner, 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
}
fn transition_type(&self) -> SignalTransitionType {
self.edge_polarity
}
fn with_transition_type(mut self, trans: SignalTransitionType) -> Self {
self.edge_polarity = trans;
self
}
}
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct NDLMDelay {
delay: [Time; 2],
}
impl NDLMDelay {
fn get(&self, minmax: MinMax) -> Time {
self.delay[minmax as usize]
}
}
impl Zero for NDLMDelay {
fn zero() -> Self {
Self {
delay: [Zero::zero(); 2],
}
}
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_rise: Capacitance,
load_capacitance_fall: Capacitance,
}
impl NDLMOutputLoad {
fn get(&self, edge_type: RiseFall) -> Capacitance {
match edge_type {
RiseFall::Rise => self.load_capacitance_rise,
RiseFall::Fall => self.load_capacitance_fall,
}
}
}
impl Zero for NDLMOutputLoad {
fn zero() -> Self {
Self {
load_capacitance_rise: Zero::zero(),
load_capacitance_fall: Zero::zero(),
}
}
fn is_zero(&self) -> bool {
self.load_capacitance_rise.is_zero() && self.load_capacitance_fall.is_zero()
}
}
impl std::ops::Add for NDLMOutputLoad {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
load_capacitance_rise: self.load_capacitance_rise + rhs.load_capacitance_rise,
load_capacitance_fall: self.load_capacitance_fall + rhs.load_capacitance_fall,
}
}
}
impl<'a, N: NetlistBase> NDLMCellModel<'a, N> {
pub fn new(library: &'a LibertyTimingLibrary<N::CellId, N::PinId>, 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_pin_table(netlist)
}
fn init_pin_table(&mut self, netlist: &N) {
for cell_id in netlist.each_cell() {
for pin_id in netlist.each_pin(&cell_id) {
let pin_data = self.library.get_pin(&cell_id, &pin_id);
if let Some(pin_data) = pin_data {
self.pin_capacitances.insert(
pin_id.clone(),
NDLMOutputLoad {
load_capacitance_rise: pin_data.capacitance_rise,
load_capacitance_fall: pin_data.capacitance_fall,
},
);
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 logic_value = if a.logic_value == b.logic_value {
a.logic_value
} else {
Default::default()
};
let reduction_fns = [Time::min, 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]));
use SignalTransitionType::*;
let edge_polarity = match (a.edge_polarity, b.edge_polarity) {
(Any, _) | (_, Any) => Any,
(Rise, Fall) | (Fall, Rise) => Any,
(Rise, _) | (_, Rise) => Rise,
(Fall, _) | (_, Fall) => Fall,
(Constant(c1), Constant(c2)) if c1 == c2 => Constant(c1),
(Constant(_), Constant(_)) => Constant(Logic3::X),
};
NDLMSignal {
edge_polarity,
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()
}
}
impl<'a, N: NetlistBase> CellDelayModel<N> for NDLMCellModel<'a, N> {
fn cell_output(
&self,
netlist: &N,
arc: &CellDelayArc<N::PinId>,
input_signal: &Self::Signal,
output_load: &Self::Load,
_other_inputs: &impl Fn(&N::PinId) -> Option<Self::LogicValue>,
) -> Option<Self::Signal> {
debug_assert!(
netlist.parent_cell_of_pin(&arc.input_pin.0)
== netlist.parent_cell_of_pin(&arc.output_pin.0),
"start and end of the delay arc must belong to the same cell."
);
let cell = netlist.parent_cell_of_pin(&arc.input_pin.0);
debug_assert_eq!(
SignalTransitionType::from(arc.input_pin.1),
input_signal.transition_type(),
"edge type of input signal is not consistent with the timing arc"
);
if let SignalTransitionType::Constant(c) = input_signal.transition_type() {
todo!("handle constant input");
}
let input_edge_polarity = match input_signal.transition_type() {
SignalTransitionType::Constant(_) => unreachable!("should be handled above"),
SignalTransitionType::Rise => RiseFall::Rise,
SignalTransitionType::Fall => RiseFall::Fall,
SignalTransitionType::Any => {
panic!("edge polarity of signal should match the edge polarity of the graph node")
}
};
assert_eq!(input_edge_polarity, arc.input_pin.1,
"Actual edge polarity of the input signal does not match the edge polarity of the graph node.");
let mut slew_cache: SmallVec<[_; 1]> = Default::default();
let mut delay_cache: SmallVec<[_; 1]> = Default::default();
let slew_fn = |minmax: MinMax| -> Option<Time> {
let input_slew = input_signal.slew(minmax);
let cached = slew_cache
.iter()
.find(|(s, _)| s == &input_slew)
.map(|(_, s)| *s);
if let Some(slew) = cached {
return slew;
}
let slew = self
.library
.get_slew(
DelayArcArg { cell: &cell, arc },
input_slew,
output_load.get(arc.output_pin.1),
)
.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((input_slew, slew));
slew
};
let delay_fn = |minmax: MinMax| -> Option<Time> {
let input_slew = input_signal.slew(minmax);
let cached = delay_cache
.iter()
.find(|(s, _)| s == &input_slew)
.map(|(_, d)| *d);
if let Some(delay) = cached {
return delay;
}
let delay = self
.library
.get_cell_delay(
DelayArcArg { cell: &cell, arc },
input_signal.slew(minmax),
output_load.get(arc.output_pin.1),
)
.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((input_slew, delay));
delay
};
let args = [MinMax::Min, MinMax::Max];
let delays = args.map(delay_fn);
let slews = args.map(slew_fn);
for i in 0..2 {
assert_eq!(
delays[i].is_some(),
slews[i].is_some(),
"delay and slew must be defined both or none of them"
);
}
let is_all_none = delays.iter().all(Option::is_none);
if is_all_none {
return Default::default();
}
let mut s = NDLMSignal::new(Time::zero(), Time::zero());
s.logic_value = match arc.output_pin.1 {
RiseFall::Rise => Logic3::H,
RiseFall::Fall => Logic3::L,
};
s.edge_polarity = arc.output_pin.1.into();
let mut slew_defined = false;
let mut delay_defined = false;
for i in 0..delays.len() {
if let Some(delay) = delays[i] {
s.delay[i] = input_signal.delay[i] + delay;
delay_defined = true;
}
if let Some(slew) = slews[i] {
s.slew[i] = slew;
slew_defined = true;
}
}
if !delay_defined && !slew_defined {
return Default::default();
}
debug_assert!(s.delay.iter().all(|d| d >= &Time::zero()));
debug_assert!(s.slew.iter().all(|s| s >= &Time::zero()));
debug_assert!(s.delay(MinMax::Max) >= s.delay(MinMax::Min));
debug_assert!(s.slew(MinMax::Max) >= s.slew(MinMax::Min));
Some(s)
}
fn delay_arcs(
&self,
_netlist: &N,
cell: &N::CellId,
) -> impl Iterator<Item = CellDelayArc<N::PinId>> + '_ {
let delay_arcs = &self
.library
.cells
.get(cell)
.expect("Cell not in library.")
.delay_arcs;
delay_arcs.into_iter().cloned()
}
}
impl<'a, N: NetlistBase> ConstraintBase for NDLMCellModel<'a, N> {
type Constraint = ();
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::*;
let d_max = actual_delay.get(Max);
let d_min = actual_delay.get(Min);
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::*;
let earliest_arrival_time = actual_signal.delay(Min);
let latest_arrival_time = actual_signal.delay(Max);
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,
arc: &CellConstraintArc<N::PinId>,
constrained_pin_signal: &Self::Signal,
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(&arc.constrained_pin.0);
debug_assert!(
{
let cell2 = netlist.parent_cell_of_pin(&arc.related_pin.0);
cell == cell2
},
"Both pins of the constraint arc must belong to the same cell."
);
let output_load = output_loads(&arc.constrained_pin.0).unwrap_or(Zero::zero());
let hold_time = |(constrained_slew, related_slew): (MinMax, MinMax)| -> Option<Time> {
let constrained_pin_transition = constrained_pin_signal.slew(constrained_slew);
let related_pin_transition = related_pin_signal.slew(related_slew);
self.library.get_hold_constraint(
ConstraintArcArg { cell: &cell, arc },
related_pin_transition,
constrained_pin_transition,
output_load.get(arc.constrained_pin.1),
)
};
let setup_time = |(constrained_slew, related_slew): (MinMax, MinMax)| -> Option<Time> {
let constrained_pin_transition = constrained_pin_signal.slew(constrained_slew);
let related_pin_transition = related_pin_signal.slew(related_slew);
self.library.get_setup_constraint(
ConstraintArcArg { cell: &cell, arc },
related_pin_transition,
constrained_pin_transition,
output_load.get(arc.constrained_pin.1),
)
};
let slew_combinations = [
(MinMax::Max, MinMax::Max),
(MinMax::Min, MinMax::Min),
(MinMax::Max, MinMax::Min),
(MinMax::Min, MinMax::Max),
];
let max_setup_time = slew_combinations
.into_iter()
.filter_map(setup_time)
.reduce(Time::max);
let max_hold_time = slew_combinations
.into_iter()
.filter_map(hold_time)
.reduce(Time::max);
if max_setup_time.is_some() || max_hold_time.is_some() {
use MinMax::*;
let t_early = related_pin_signal.delay(Min);
let t_late = related_pin_signal.delay(Max);
let earliest_arrival_time = max_hold_time.map(|t_ho| t_early + t_ho);
let latest_arrival_time = max_setup_time.map(|t_su| t_late - t_su);
Some(NDLMRequiredSignal {
earliest_arrival_time,
latest_arrival_time,
})
} else {
None }
}
fn constraint_arcs(
&self,
_netlist: &N,
cell_id: &N::CellId,
) -> impl Iterator<Item = CellConstraintArc<N::PinId>> + '_ {
let cell = self
.library
.cells
.get(cell_id)
.expect("Cell not in library.");
let constraint_arcs: HashSet<_> = cell
.pins
.iter()
.flat_map(move |(constrained_pin, pin)| {
let constraint_arcs = pin
.timing
.setup_arcs
.iter()
.chain(pin.timing.hold_arcs.iter());
constraint_arcs.flat_map(|((related_pin, related_edge), arc)| {
[
(&arc.rise_constraint, RiseFall::Rise),
(&arc.fall_constraint, RiseFall::Fall),
]
.into_iter()
.filter(|(lut, _)| lut.is_some())
.map(|(_, constrained_edge)| CellConstraintArc {
related_pin: (related_pin.clone(), *related_edge),
constrained_pin: (constrained_pin.clone(), constrained_edge),
})
})
})
.collect();
constraint_arcs.into_iter()
}
}
impl<'a, N: NetlistBase> LogicModel<N> for NDLMCellModel<'a, N> {
fn pin_function(
&self,
output_pin: &<N as NetlistIds>::PinId,
) -> &cell_logic_model::OutputFunction<N::PinId> {
&self
.pin_data
.get(output_pin)
.expect("pin not found")
.output_function
}
fn timing_sense(
&self,
output_pin: &N::PinId,
related_pin: &N::PinId,
_other_inputs: &impl Fn(&N::PinId) -> Option<bool>,
) -> Unateness {
self.pin_data
.get(output_pin)
.expect("pin not found")
.timing
.get_delay_lut(&(related_pin.clone(), RiseFall::Rise)) .expect("related pin not found")
.timing_sense
}
}