use num_traits::Zero;
use smallvec::*;
use std::{borrow::Borrow, marker::PhantomData, sync::atomic::Ordering};
use libreda_db::{prelude as db, reference_access::NetlistReferenceAccess};
use petgraph::{
data::DataMap,
visit::{Data, EdgeRef, GraphBase, IntoEdgesDirected},
};
use crate::{
cone_propagation::{self, ConePropagationOp, PropagationDirection},
timing_graph::*,
traits::*,
};
use pargraph::{
executors::multi_thread::MultiThreadExecutor,
local_view::{FullConflictDetection, LocalGraphView, SyncReadGuard},
worklists::{biglock_fifo::FifoWorklist, WorklistPush},
BorrowDataCell, DataConflictErr, LabellingOperator,
};
pub(crate) fn propagate_signals_incremental<Netlist, CellModel, ICModel, ICLoadModel>(
netlist: &Netlist,
g: &TimingGraph<Netlist, CellModel>,
cell_model: &CellModel,
interconnect_delay_model: &ICModel,
interconnect_load_model: &ICLoadModel,
frontier: impl Iterator<Item = petgraph::stable_graph::NodeIndex> + Clone,
generation: u32,
) where
Netlist: db::NetlistBaseMT,
CellModel: ConstraintBase + CellDelayModel<Netlist> + CellConstraintModel<Netlist> + Sync,
ICModel: InterconnectDelayModel<Netlist, Signal = CellModel::Signal> + Sync,
ICLoadModel: InterconnectLoadModel<Netlist, Load = CellModel::Load> + Sync,
{
let executor = MultiThreadExecutor::new();
debug_assert!(
frontier.clone().all(|n| {
g.arc_graph
.node_weight(n)
.unwrap()
.generation_forward
.load(Ordering::Relaxed)
< generation
}),
"`generation` must monotonically increase with each call"
);
{
let operator = ConePropagationOp { generation };
let worklist = FifoWorklist::new_with_local_queues(
frontier
.clone()
.map(cone_propagation::WorkItem::new_forward)
.collect(),
);
executor.run_readonly(worklist, operator, &g.arc_graph);
}
let initial_nodes = frontier
.filter(|n| {
let data = g.arc_graph.node_weight(*n).unwrap();
data.forward_dependencies.num_unresolved() == 0
})
.map(WorkItem::new_forward);
let propagation_op = PropagationOp::new(
netlist,
cell_model,
interconnect_delay_model,
interconnect_load_model,
generation,
);
let worklist = FifoWorklist::new_global_queue(initial_nodes.collect());
executor.run_node_labelling(worklist, &propagation_op, &g.arc_graph);
}
#[derive(Clone)]
struct PropagationOp<'a, G, N, D, IC, ICL> {
netlist: &'a N,
cell_model: &'a D,
interconnect_delay_model: &'a IC,
interconnect_load_model: &'a ICL,
generation: u32,
_graph_type: PhantomData<G>,
_node_data_type: PhantomData<N>,
}
impl<'a, G, N, C, IC, ICL> PropagationOp<'a, G, N, C, IC, ICL>
where
N: db::NetlistBase,
C: DelayBase + ConstraintBase + CellDelayModel<N> + CellConstraintModel<N>,
IC: InterconnectDelayModel<N, Signal = C::Signal>,
ICL: InterconnectLoadModel<N, Load = C::Load>,
G: GraphBase
+ Data<NodeWeight = NodeData<N, C>, EdgeWeight = EdgeData<C>>
+ IntoEdgesDirected
+ DataMap,
G::NodeId: std::fmt::Debug,
{
fn new(
netlist: &'a N,
delay_model: &'a C,
interconnect_delay_model: &'a IC,
interconnect_load_model: &'a ICL,
generation: u32,
) -> Self {
Self {
generation,
netlist,
cell_model: delay_model,
_node_data_type: Default::default(),
_graph_type: Default::default(),
interconnect_delay_model,
interconnect_load_model,
}
}
fn evaluate_delay_arc(
&self,
netlist: &N,
local_view: &LocalGraphView<&G, FullConflictDetection>,
input_signal: &C::Signal,
output_load: &C::Load,
graph_edge: G::EdgeRef,
other_inputs: &impl Fn(&N::PinId) -> Option<C::LogicValue>,
) -> Option<C::Signal> {
let source = graph_edge.source();
let target = graph_edge.target();
let get_terminal = |node_id| {
let node = &local_view
.node_weight(node_id)
.expect("node has no weight")
.node_type;
match node {
GraphNodeType::Terminal(t) => Some(netlist.terminal_ref(t)),
_ => None,
}
};
let source_node = get_terminal(source)?;
let target_node = get_terminal(target)?;
match (&source_node, &target_node) {
(db::TerminalRef::PinInst(s), db::TerminalRef::PinInst(t)) => {
let input_pin = s.pin().id();
let output_pin = t.pin().id();
let is_intra_cell =
s.pin().direction().is_input() && t.pin().direction().is_output();
if is_intra_cell {
assert_eq!(
s.cell_instance(),
t.cell_instance(),
"pins must belong to the same cell instance"
);
self.cell_model.cell_output(
self.netlist,
&input_pin,
input_signal,
&output_pin,
output_load,
other_inputs,
)
} else {
let output_load = Zero::zero(); self.interconnect_delay_model.interconnect_output(
netlist,
&source_node.id(),
input_signal,
&target_node.id(),
&output_load,
)
}
}
_ => {
let output_load = Zero::zero(); self.interconnect_delay_model.interconnect_output(
netlist,
&source_node.id(),
input_signal,
&target_node.id(),
&output_load,
)
}
}
}
fn evaluate_constraint_arc(
&self,
local_view: &LocalGraphView<&G, FullConflictDetection>,
local_data: &SyncNodeData<C>,
constraint_edge: G::EdgeRef,
other_inputs: &impl Fn(&N::PinId) -> Option<C::Signal>,
output_loads: &impl Fn(&N::PinId) -> Option<C::Load>,
) -> Option<C::RequiredSignal> {
let netlist = self.netlist;
let constrained_node = constraint_edge.target();
let related_node = constraint_edge.source();
let constrained_node_weight = local_view.node_weight(constrained_node).unwrap();
let related_node_weight = local_view.node_weight(related_node).unwrap();
let constrained_node_data = local_data;
let related_node_data = related_node_weight
.borrow_data_cell()
.try_read()
.expect("data of constrained node is locked");
let get_terminal = |node_type: &_| match node_type {
GraphNodeType::Terminal(t) => Some(netlist.terminal_ref(t)),
_ => None,
};
let constrained_terminal = get_terminal(&constrained_node_weight.node_type)?;
let related_terminal = get_terminal(&related_node_weight.node_type)?;
match (&related_terminal, &constrained_terminal) {
(db::TerminalRef::PinInst(r), db::TerminalRef::PinInst(c)) => {
let related_pin = r.pin().id();
let constrained_pin = c.pin().id();
let is_intra_cell =
r.pin().direction().is_input() && c.pin().direction().is_input();
if is_intra_cell {
assert_eq!(
r.cell_instance(),
c.cell_instance(),
"pins must belong to the same cell instance"
);
let constrained_pin_signal = constrained_node_data
.signal
.as_ref()
.expect("actual arrival time at constrained pin is not known yet");
let related_pin_signal = related_node_data
.signal
.as_ref()
.expect("actual arrival time at related pin is not known yet");
self.cell_model.get_required_input(
netlist,
&c.pin().id(),
constrained_pin_signal,
&r.pin().id(),
related_pin_signal,
other_inputs,
output_loads,
)
} else {
let output_load = C::Load::zero(); todo!()
}
}
_ => {
todo!("interconnect constraint");
}
}
}
fn mark_forward_dependency_resolved(
&self,
local_view: &LocalGraphView<&G, FullConflictDetection>,
push_to_worklist: &mut impl FnMut(WorkItem<G::NodeId>),
) {
let output_edges =
local_view.edges_directed(local_view.active_node(), petgraph::Direction::Outgoing);
for e in output_edges {
let output_node = e.target();
let edge_type = e.weight().edge_type;
let unsync_data = local_view
.node_weight(output_node)
.expect("output node has no weight");
match edge_type {
GraphEdgeType::Delay => {
let num_unresolved_dependencies =
unsync_data.forward_dependencies.decrement_unresolved();
if num_unresolved_dependencies == 0 {
push_to_worklist(WorkItem::new_forward(output_node));
}
}
GraphEdgeType::Constraint => {}
GraphEdgeType::Virtual => {}
}
match edge_type {
GraphEdgeType::Virtual => {}
GraphEdgeType::Delay => {}
GraphEdgeType::Constraint => {
let num_unresolved_dependencies =
unsync_data.backward_dependencies.decrement_unresolved();
if num_unresolved_dependencies == 0 {
push_to_worklist(WorkItem::new_backward(output_node));
}
}
}
}
{
let num_unresolved = local_view
.node_weight(local_view.active_node())
.unwrap()
.backward_dependencies
.decrement_unresolved();
if num_unresolved == 0 {
push_to_worklist(WorkItem::new_backward(local_view.active_node()))
}
}
}
fn mark_backward_dependency_resolved(
&self,
local_view: &LocalGraphView<&G, FullConflictDetection>,
push_to_worklist: &mut impl FnMut(WorkItem<G::NodeId>),
) {
let input_edges = local_view
.edges_directed(local_view.active_node(), petgraph::Direction::Incoming)
.filter(|e| e.weight().edge_type.is_delay_arc());
for e in input_edges {
let input_node = e.source();
let unsync_data = local_view
.node_weight(input_node)
.expect("input node has no weight");
match unsync_data.node_type {
GraphNodeType::Terminal(_) => {}
_ => continue, }
let num_unresolved_dependencies =
unsync_data.backward_dependencies.decrement_unresolved();
if num_unresolved_dependencies == 0 {
push_to_worklist(WorkItem::new_backward(input_node));
}
}
}
fn propagate_forward(
&self,
local_view: &LocalGraphView<&G, FullConflictDetection>,
local_data: &mut SyncNodeData<C>,
mut push_to_worklist: impl FnMut(WorkItem<G::NodeId>),
) -> Result<(), DataConflictErr<G::NodeId, G::EdgeId>> {
let active_node = local_view.active_node();
let unsync_data = local_view.node_weight(active_node).unwrap();
debug_assert_eq!(
unsync_data.generation_forward.load(Ordering::Relaxed),
self.generation,
"node is not marked for backward propagation"
);
if !local_data.is_primary_input {
let input_edges = local_view
.edges_directed(active_node, petgraph::Direction::Incoming)
.filter(|e| e.weight().edge_type.is_delay_arc());
let inputs_with_data: Result<
SmallVec<[(G::EdgeRef, SyncReadGuard<G::NodeWeight>); 8]>,
_,
> = input_edges
.map(|e| {
local_view
.try_node_weight(e.source())
.expect("node has no weight")
.map(|data| (e, data))
})
.collect();
let inputs_with_data = inputs_with_data?; let output_load = match &unsync_data.node_type {
GraphNodeType::Terminal(t) => self
.interconnect_load_model
.interconnect_load(self.netlist, t),
_ => Zero::zero(),
};
let other_inputs = |pin: &N::PinId| -> Option<C::LogicValue> {
inputs_with_data.iter().find_map(|(edge_ref, data)| {
let src = edge_ref.source();
let node_data = local_view.node_weight(src).unwrap();
let found = match &node_data.node_type {
GraphNodeType::Terminal(t) => match t {
db::TerminalId::PinId(p) => pin == p,
db::TerminalId::PinInstId(p_inst) => {
&self.netlist.template_pin(p_inst) == pin
}
},
_ => false,
};
found
.then(|| data.signal.as_ref())
.flatten()
.map(|s| s.logic_value())
})
};
local_data.signal = inputs_with_data
.iter()
.filter_map(|(input_edge, input_node_data)| {
input_node_data.signal.as_ref().and_then(|input_signal| {
let output_signal = self.evaluate_delay_arc(
self.netlist,
local_view,
input_signal,
&output_load,
*input_edge,
&other_inputs,
);
let mut w = input_edge
.weight()
.borrow_data_cell()
.try_write(0)
.expect("failed to acquire write lock");
w.delay = output_signal
.as_ref()
.map(|o| self.cell_model.get_delay(input_signal, o));
output_signal
})
})
.reduce(|s1, s2| self.cell_model.summarize_delays(&s1, &s2));
} else {
assert!(
local_data.signal.is_some(),
"Primary inputs must have their actual signal specified."
);
}
self.mark_forward_dependency_resolved(local_view, &mut push_to_worklist);
Ok(())
}
fn propagate_backward(
&self,
local_view: &LocalGraphView<&G, FullConflictDetection>,
local_data: &mut SyncNodeData<C>,
mut push_to_worklist: impl FnMut(WorkItem<G::NodeId>),
) -> Result<(), DataConflictErr<G::NodeId, G::EdgeId>> {
let active_node = local_view.active_node();
let unsync_data = local_view.node_weight(active_node).unwrap();
debug_assert_eq!(
unsync_data.generation_backward.load(Ordering::Relaxed),
self.generation,
"node is not marked for backward propagation"
);
let output_delay_arcs = local_view
.edges_directed(active_node, petgraph::Direction::Outgoing)
.filter(|e| e.weight().edge_type.is_delay_arc());
let input_constraint_arcs = local_view
.edges_directed(active_node, petgraph::Direction::Incoming)
.filter(|e| e.weight().edge_type.is_constraint_arc());
let outputs_with_data: Result<
SmallVec<[(G::EdgeRef, SyncReadGuard<G::NodeWeight>); 8]>,
_,
> = output_delay_arcs
.map(|e| {
local_view
.try_node_weight(e.target())
.expect("node has no weight")
.map(|data| (e, data))
})
.collect();
let outputs_with_data = outputs_with_data?; let constraint_inputs_with_data: Result<
SmallVec<[(G::EdgeRef, SyncReadGuard<G::NodeWeight>); 8]>,
_,
> = input_constraint_arcs
.map(|e| {
local_view
.try_node_weight(e.source())
.expect("node has no weight")
.map(|data| (e, data))
})
.collect();
assert!(constraint_inputs_with_data.is_ok());
let constraint_inputs_with_data = constraint_inputs_with_data?; let required_signals_from_delays = outputs_with_data
.into_iter()
.filter(|(_, output_data)| output_data.required_signal.is_some())
.map(|(delay_edge, output_data)| {
let edge_weight = delay_edge
.weight()
.borrow_data_cell()
.try_read()
.expect("Edge data should not be locked anymore.");
let actual_delay = edge_weight
.delay
.as_ref()
.expect("Delay should already be computed.");
let required_output = output_data.required_signal.as_ref().unwrap(); let actual_input = local_data
.signal
.as_ref()
.expect("Actual signal is not computed yet.");
self.cell_model
.solve_delay_constraint(actual_delay, required_output, actual_input)
});
let required_signals_from_constraints =
constraint_inputs_with_data.into_iter().filter_map(
|(constraint_edge, constraint_node_data)| {
self.evaluate_constraint_arc(
local_view,
local_data,
constraint_edge,
&|_pin| None,
&|pin| {
Some(self.interconnect_load_model.interconnect_load(
self.netlist,
&db::TerminalId::PinId(pin.clone()),
))
},
)
},
);
let required_signal = required_signals_from_delays
.chain(required_signals_from_constraints)
.reduce(|r1, r2| self.cell_model.summarize_constraints(&r1, &r2));
local_data.required_signal = required_signal;
self.mark_backward_dependency_resolved(local_view, &mut push_to_worklist);
Ok(())
}
}
impl<'a, G, N, D, IC, ICL> LabellingOperator<G> for &PropagationOp<'a, G, N, D, IC, ICL>
where
N: db::NetlistBase,
D: DelayBase + ConstraintBase + CellDelayModel<N> + CellConstraintModel<N>,
IC: InterconnectDelayModel<N, Signal = D::Signal>,
ICL: InterconnectLoadModel<N, Load = D::Load>,
G: GraphBase
+ Data<NodeWeight = NodeData<N, D>, EdgeWeight = EdgeData<D>>
+ IntoEdgesDirected
+ DataMap,
G::NodeId: std::fmt::Debug,
{
type NodeWeight = SyncNodeData<D>;
type WorkItem = WorkItem<G::NodeId>;
fn op(
&self,
work_item: Self::WorkItem,
local_view: LocalGraphView<&G, FullConflictDetection>,
local_data: &mut Self::NodeWeight,
mut worklist: impl WorklistPush<Self::WorkItem>,
) -> Result<(), DataConflictErr<G::NodeId, G::EdgeId>> {
match work_item.dir {
PropagationDirection::Forward => {
self.propagate_forward(&local_view, local_data, |n| worklist.push(n))?;
}
PropagationDirection::Backward => {
self.propagate_backward(&local_view, local_data, |n| worklist.push(n))?;
}
};
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct WorkItem<N> {
node: N,
dir: PropagationDirection,
}
impl<N> WorkItem<N> {
pub fn new_forward(n: N) -> Self {
Self {
node: n,
dir: PropagationDirection::Forward,
}
}
fn new_backward(n: N) -> Self {
Self {
node: n,
dir: PropagationDirection::Backward,
}
}
}
impl<N> Borrow<N> for WorkItem<N> {
fn borrow(&self) -> &N {
&self.node
}
}