1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// SPDX-FileCopyrightText: 2023 Thomas Kramer <code@tkramer.ch>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

//! Propagate forward and backward cones from a set of frontier nodes.
//! The cones mark the regions where the timing must potentially be updated.

use std::{borrow::Borrow, sync::atomic::Ordering};

use libreda_db::traits::{NetlistBaseMT, NetlistIds};
use petgraph::{
    data::DataMap,
    stable_graph::NodeIndex,
    visit::{EdgeRef, GraphBase, IntoEdgesDirected},
};

use crate::{
    timing_graph::{EdgeData, GraphEdgeType, NodeData, TimingGraph},
    traits::ConstraintBase,
};

use pargraph::{
    executors::multi_thread::MultiThreadExecutor, local_view::LocalGraphView,
    worklists::biglock_fifo::FifoWorklist, worklists::WorklistPush, ReadonlyOperator,
};

/// Mark the fan-out cones of the supplied `frontier`-nodes with the current
/// `generation` number. Let the set of nodes in this fan-out cones be `C_fwd`.
/// Then mark also the nodes in the fanin-cones of the nodes in `C_fwd` with
/// the current `generation` number.
///
/// This is used as a preliminary step of incremental timing propagation.
/// Only the nodes from `C_fwd` will need to update their actual signals.
/// And only the nodes from `C_bwd` will need to update their required signals.
pub(crate) fn propagate_cones<Netlist, CellModel>(
    g: &TimingGraph<Netlist, CellModel>,
    frontier: impl Iterator<Item = NodeIndex>,
    generation: u32,
) where
    Netlist: NetlistBaseMT,
    CellModel: ConstraintBase,
{
    let operator = ConePropagationOp { generation };

    let executor = MultiThreadExecutor::new();

    let worklist =
        FifoWorklist::new_with_local_queues(frontier.map(WorkItem::new_forward).collect());
    executor.run_readonly(worklist, operator, &g.arc_graph);
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum PropagationDirection {
    Forward,
    Backward,
}

#[derive(Clone)]
pub(crate) struct ConePropagationOp {
    pub generation: u32,
}

/// Work item associated with a propagation direction.
#[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
    }
}

impl<G, N, D> ReadonlyOperator<G> for ConePropagationOp
where
    N: NetlistIds,
    D: ConstraintBase,
    G: GraphBase
        + IntoEdgesDirected
        + DataMap<NodeWeight = NodeData<N, D>, EdgeWeight = EdgeData<D>>,
{
    type WorkItem = WorkItem<G::NodeId>;

    fn op(
        &self,
        work_item: Self::WorkItem,
        local_view: LocalGraphView<&G>,
        mut worklist: impl WorklistPush<Self::WorkItem>,
    ) {
        let local_view = &local_view;
        let active_node = local_view.active_node();

        // Find next neighbors depending on propagation direction.
        let next_neighbors = |direction: PropagationDirection| {
            let dir = match direction {
                PropagationDirection::Forward => petgraph::Direction::Outgoing,
                PropagationDirection::Backward => petgraph::Direction::Incoming,
            };

            local_view
                .edges_directed(active_node, dir)
                .map(move |e| match direction {
                    PropagationDirection::Forward => (e.target(), e.weight().edge_type),
                    PropagationDirection::Backward => (e.source(), e.weight().edge_type),
                })
        };

        let local_data = local_view.node_weight(active_node).unwrap();

        if work_item.dir == PropagationDirection::Forward {
            let is_forward_visited = local_data
                .generation_forward
                .swap(self.generation, Ordering::Relaxed)
                == self.generation;

            // Do forward propagation only, if the active node was
            // reached by forward propagation or if the active node
            // is an initial frontier node.
            if !is_forward_visited {
                // The active node must be forward-propagated before it can be backward-propagated.
                local_data.backward_dependencies.increment_unresolved();

                for (n, edge_type) in next_neighbors(PropagationDirection::Forward) {
                    let data = local_view.node_weight(n).unwrap();

                    // Keep track of the number of unresolved dependencies.
                    match edge_type {
                        GraphEdgeType::Delay | GraphEdgeType::Virtual => {
                            data.forward_dependencies.increment_unresolved();
                        }
                        GraphEdgeType::Constraint => {
                            data.backward_dependencies.increment_unresolved();
                        }
                    }

                    worklist.push(WorkItem::new_forward(n));
                }
            }
        }

        let is_backward_visited = local_data
            .generation_backward
            .swap(self.generation, Ordering::Relaxed)
            == self.generation;

        if is_backward_visited {
            // Nothing to do.
            return;
        }

        // Always do backward propagation.
        for (n, edge_type) in next_neighbors(PropagationDirection::Backward) {
            let data = local_view.node_weight(n).unwrap();

            // Keep track of the number of unresolved dependencies.
            match edge_type {
                GraphEdgeType::Delay | GraphEdgeType::Virtual => {
                    data.backward_dependencies.increment_unresolved();
                }
                GraphEdgeType::Constraint => {}
                GraphEdgeType::Virtual => {}
            }

            worklist.push(WorkItem::new_backward(n));
        }
    }
}