libreda_sta/
timing_graph.rs

1// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5//! Construct timing graphs (delay graph, constraint graph) from a netlist.
6
7use std::sync::atomic::AtomicU32;
8use std::sync::atomic::Ordering;
9
10use fnv::FnvHashMap;
11use libreda_db::reference_access::*;
12use libreda_db::traits::*;
13use pargraph::id_rwlock::IdLockWriteGuard;
14use petgraph::visit::EdgeRef;
15use smallvec::SmallVec;
16
17use crate::traits::CellConstraintModel;
18use crate::traits::ConstraintBase;
19use crate::traits::{CellDelayModel, TimingBase};
20use crate::RiseFall;
21use crate::StaError;
22use crate::PATH_SEPARATOR;
23use fnv::FnvHashSet;
24use libreda_db::prelude as db;
25use pargraph::id_rwlock::IdLockReadGuard;
26use pargraph::BorrowDataCell;
27use pargraph::DataCell;
28use petgraph::prelude::NodeIndex;
29use petgraph::stable_graph::StableDiGraph;
30
31/// Data of a node in the timing graph.
32/// Holds timing information such as actual and required arrival times but
33/// also data used for concurrent graph operations.
34#[derive(Debug)]
35pub(crate) struct NodeData<N, D>
36where
37    N: NetlistIds,
38    D: TimingBase + ConstraintBase,
39{
40    /// Type of the node. Also makes a link to the corresponding pin or pin-instance in the netlist.
41    pub node_type: GraphNodeType<N>,
42    /// Node data which can be accessed for read/write during parallel graph operations.
43    sync_data: DataCell<SyncNodeData<D>>,
44    /// The level of this node (minimal number of hops to a node without inputs)
45    pub forward_level: AtomicU32,
46    /// Count the number of unresolved input nodes.
47    pub forward_dependencies: DependencyCounter,
48    /// Count the number of unresolved output nodes + the current node.
49    /// Note the node itself is considered a backward dependency because
50    /// the back-propgation depends on the presence of the actual signal.
51    pub backward_dependencies: DependencyCounter,
52    /// ID of the last iteration in which this node was touched for forward propagation.
53    /// Used to mark nodes for incremental updates.
54    pub generation_forward: AtomicU32,
55    /// ID of the last iteration in which this node was touched for backward propagation.
56    /// Used to mark nodes for incremental updates.
57    pub generation_backward: AtomicU32,
58}
59
60impl<N, D> NodeData<N, D>
61where
62    N: NetlistBase,
63    D: TimingBase + ConstraintBase,
64{
65    pub fn new(node_type: GraphNodeType<N>) -> Self {
66        Self {
67            node_type,
68            sync_data: Default::default(),
69            forward_level: Default::default(),
70            generation_forward: Default::default(),
71            generation_backward: Default::default(),
72            forward_dependencies: Default::default(),
73            backward_dependencies: Default::default(),
74        }
75    }
76}
77
78impl<N, D> BorrowDataCell for NodeData<N, D>
79where
80    N: NetlistBase,
81    D: TimingBase + ConstraintBase,
82{
83    type UserData = SyncNodeData<D>;
84
85    fn borrow_data_cell(&self) -> &DataCell<Self::UserData> {
86        &self.sync_data
87    }
88}
89
90/// Atomic counter unresolved dependencies of graph nodes.
91#[derive(Debug, Default)]
92pub(crate) struct DependencyCounter {
93    /// Number of unresolved dependencies.
94    num_unresolved: AtomicU32,
95}
96
97impl DependencyCounter {
98    /// Get number of unresolved dependencies.
99    /// Loaded using relaxed memory ordering.
100    pub fn num_unresolved(&self) -> u32 {
101        self.num_unresolved.load(Ordering::Relaxed)
102    }
103
104    /// Set the number of unresolved dependencies.
105    /// Uses relaxed memory ordering.
106    pub fn set_num_unresolved(&self, num_unresolved: u32) {
107        self.num_unresolved.store(num_unresolved, Ordering::Relaxed);
108    }
109
110    /// Increment the number of unresolved dependencies and return
111    /// the result.
112    pub fn increment_unresolved(&self) -> u32 {
113        self.num_unresolved.fetch_add(1, Ordering::Relaxed) + 1
114    }
115
116    /// Decrement the number of unresolved dependencies and return
117    /// the result.
118    /// # Panics
119    /// Panics on an integer underflow.
120    pub fn decrement_unresolved(&self) -> u32 {
121        let r = self.num_unresolved.fetch_sub(1, Ordering::Relaxed);
122        assert!(r > 0, "integer underflow in number of dependencies");
123        r - 1
124    }
125}
126
127/// Node data which we can access during parallel graph operations.
128/// Access to this data is synchronized by a lock in the `DataCell` struct.
129#[derive(Debug, Clone)]
130pub(crate) struct SyncNodeData<D>
131where
132    D: TimingBase + ConstraintBase,
133{
134    /// Mark this node as a primary input. Primary inputs
135    /// have the actual signal set by the user. The actual signal of a primary input
136    /// thus does not need to be computed.
137    pub is_primary_input: bool,
138    /// The signal at this location in the netlist.
139    /// This is typically used to represent the arrival time.
140    pub signal: Option<D::Signal>,
141    /// The required signal which is necessary to meet constraints in the netlist.
142    /// Typically this is encodes the required arrival time.
143    pub required_signal: Option<D::RequiredSignal>,
144}
145
146impl<D> Default for SyncNodeData<D>
147where
148    D: TimingBase + ConstraintBase,
149{
150    fn default() -> Self {
151        Self {
152            signal: Default::default(),
153            required_signal: Default::default(),
154            is_primary_input: false,
155        }
156    }
157}
158
159/// Node in the timing graph.
160/// Most nodes represent a pin in the netlist. There are also a few virtual nodes
161/// (source nodes) which simplify the implementation of the delay propagation algorithms.
162#[derive(Clone)]
163pub(crate) enum GraphNodeType<N: NetlistIds> {
164    /// A virtual node.
165    /// Used as a start node in the delay propagation algorithm.
166    ForwardPropagationSource,
167    /// A virtual node which represents the starting node for backpropagating the constraints.
168    BackwardPropagationSource,
169    /// A node which represents a pin of a circuit component.
170    /// Used for rising signal edges.
171    TerminalRise(db::TerminalId<N>),
172    /// A node which represents a pin of a circuit component.
173    /// Used for falling signal edges.
174    TerminalFall(db::TerminalId<N>),
175}
176
177impl<N> std::fmt::Debug for GraphNodeType<N>
178where
179    N: NetlistIds,
180    N::PinId: std::fmt::Debug,
181    N::PinInstId: std::fmt::Debug,
182{
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        match self {
185            GraphNodeType::ForwardPropagationSource => write!(f, "source"),
186            GraphNodeType::BackwardPropagationSource => write!(f, "sink"),
187            GraphNodeType::TerminalRise(t) => write!(f, "TerminalRise({:?})", t),
188            GraphNodeType::TerminalFall(t) => write!(f, "TerminalFall({:?})", t),
189        }
190    }
191}
192
193#[derive(Debug)]
194pub(crate) struct EdgeData<T: ConstraintBase> {
195    pub edge_type: GraphEdgeType,
196    pub sync_data: DataCell<SyncEdgeData<T>>,
197}
198
199impl<T> EdgeData<T>
200where
201    T: ConstraintBase,
202{
203    pub fn new(edge_type: GraphEdgeType) -> Self {
204        Self {
205            edge_type,
206            sync_data: Default::default(),
207        }
208    }
209}
210impl<D> BorrowDataCell for EdgeData<D>
211where
212    D: TimingBase + ConstraintBase,
213{
214    type UserData = SyncEdgeData<D>;
215
216    fn borrow_data_cell(&self) -> &DataCell<Self::UserData> {
217        &self.sync_data
218    }
219}
220
221/// Edge data which we can access during parallel graph operations.
222/// Access to this data is synchronized by a lock in the `DataCell` struct.
223#[derive(Debug, Clone)]
224pub(crate) struct SyncEdgeData<D>
225where
226    D: TimingBase + ConstraintBase,
227{
228    pub delay: Option<D::Delay>,
229    pub constraint: Option<D::Constraint>, // TODO: remove
230}
231
232impl<D> Default for SyncEdgeData<D>
233where
234    D: TimingBase + ConstraintBase,
235{
236    fn default() -> Self {
237        Self {
238            delay: Default::default(),
239            constraint: Default::default(),
240        }
241    }
242}
243
244#[derive(Copy, Clone, PartialEq, Eq, Debug)]
245pub(crate) enum GraphEdgeType {
246    Delay,
247    Constraint,
248    Virtual,
249}
250
251impl GraphEdgeType {
252    pub fn is_delay_arc(&self) -> bool {
253        matches!(self, Self::Delay)
254    }
255
256    pub fn is_constraint_arc(&self) -> bool {
257        matches!(self, Self::Constraint)
258    }
259}
260
261#[derive(Debug)]
262pub(crate) struct TimingGraph<N: NetlistIds, T: ConstraintBase> {
263    /// Graph structure holding all delay and constraint arcs. Their value corresponds to the delay.
264    pub(crate) arc_graph: StableDiGraph<NodeData<N, T>, EdgeData<T>>,
265    /// The source node is used as a starting point for computing the actual arrival times.
266    /// All primary inputs to the combinational logic are connected to this source node.
267    pub(crate) aat_source_node: NodeIndex,
268    /// The source node is used as a starting point for computing the required arrival times.
269    /// All constrained pins are connected to this source node.
270    pub(crate) rat_source_node: NodeIndex,
271    /// Mapping from graph nodes to terminal IDs in the netlist.
272    // TODO: Remove. This information is redundant.
273    pub(crate) node2term: FnvHashMap<NodeIndex, db::TerminalId<N>>,
274    /// Mapping from terminal IDs in the netlist to graph nodes.
275    /// Each terminal maps to a node for rising edges and a node for falling edges.
276    pub(crate) term2node: FnvHashMap<db::TerminalId<N>, [NodeIndex; 2]>,
277    /// List of nodes from which a timing change originates.
278    /// They are used to find the forward-cone and backward-code which are
279    /// subject to change during an incremental update.
280    frontier: FnvHashSet<NodeIndex>,
281}
282
283impl<N: NetlistBase, T: ConstraintBase> TimingGraph<N, T> {
284    /// Create empty timing graph.
285    pub fn new() -> Self {
286        let mut delay_arc_graph: StableDiGraph<_, _> = Default::default();
287        let source_node =
288            delay_arc_graph.add_node(NodeData::new(GraphNodeType::ForwardPropagationSource));
289        let sink_node =
290            delay_arc_graph.add_node(NodeData::new(GraphNodeType::BackwardPropagationSource));
291        Self {
292            arc_graph: delay_arc_graph,
293            node2term: Default::default(),
294            term2node: Default::default(),
295            aat_source_node: source_node,
296            rat_source_node: sink_node,
297            frontier: Default::default(),
298        }
299    }
300
301    /// Get read access to the node data.
302    /// Returns `None` if the terminal does not exist in the timing graph.
303    /// Panics, if the read-lock cannot be acquired. Use only while there's no timing propagation running!
304    pub fn get_terminal_data(
305        &self,
306        pin: &db::TerminalId<N>,
307        edge_polarity: RiseFall,
308    ) -> Option<IdLockReadGuard<SyncNodeData<T>>> {
309        let graph_node = self.term2node.get(pin)?[edge_polarity as usize];
310        self.get_node_data(graph_node)
311    }
312    /// Get read access to the node data.
313    /// Returns `None` if the terminal does not exist in the timing graph.
314    /// Panics, if the read-lock cannot be acquired. Use only while there's no timing propagation running!
315    pub fn get_node_data(&self, graph_node: NodeIndex) -> Option<IdLockReadGuard<SyncNodeData<T>>> {
316        self.arc_graph
317            .node_weight(graph_node)
318            .expect("node has no weight")
319            .borrow_data_cell()
320            .try_read()
321            .expect("failed to acquire read-lock")
322            .into()
323    }
324
325    /// Get write access to the node data.
326    /// Returns `None` if the terminal does not exist in the timing graph.
327    /// Panics, if the write-lock cannot be acquired. Use only while there's no timing propagation running!
328    pub fn get_terminal_data_mut(
329        &self,
330        pin: &db::TerminalId<N>,
331        edge_polarity: RiseFall,
332    ) -> Option<IdLockWriteGuard<SyncNodeData<T>>> {
333        let idx = match edge_polarity {
334            RiseFall::Rise => 0,
335            RiseFall::Fall => 1,
336        };
337        let graph_node = self.term2node.get(pin)?[idx];
338
339        self.get_node_data_mut(graph_node)
340    }
341
342    /// Get write access to the node data.
343    /// Returns `None` if the terminal does not exist in the timing graph.
344    /// Panics, if the write-lock cannot be acquired. Use only while there's no timing propagation running!
345    pub fn get_node_data_mut(
346        &self,
347        graph_node: NodeIndex,
348    ) -> Option<IdLockWriteGuard<SyncNodeData<T>>> {
349        self.arc_graph
350            .node_weight(graph_node)
351            .expect("node has no weight")
352            .borrow_data_cell()
353            .try_write(0)
354            .expect("failed to acquire write-lock")
355            .into()
356    }
357
358    /// Take the set of frontier nodes and replace it by an empty set.
359    pub fn take_frontier(&mut self) -> FnvHashSet<NodeIndex> {
360        std::mem::take(&mut self.frontier)
361    }
362
363    /// Mark a node which needs to be recomputed in the next incremental update.
364    /// * `edge_polarity`: specify the rising, falling or both (`None`) node
365    pub fn add_terminal_to_frontier(
366        &mut self,
367        terminal: &db::TerminalId<N>,
368        edge_polarity: Option<RiseFall>,
369    ) {
370        let nodes = self
371            .term2node
372            .get(terminal)
373            .expect("terminal does not exist in timing graph");
374
375        match edge_polarity {
376            Some(RiseFall::Rise) => {
377                self.add_node_to_frontier(nodes[0]);
378            }
379            Some(RiseFall::Fall) => {
380                self.add_node_to_frontier(nodes[1]);
381            }
382            None => {
383                for node in nodes {
384                    self.frontier.insert(*node);
385                }
386            }
387        };
388    }
389
390    /// Mark a node which needs to be recomputed in the next incremental update.
391    pub fn add_node_to_frontier(&mut self, node: NodeIndex) {
392        self.frontier.insert(node);
393    }
394
395    /// Assemble a directed graph which represents all delays arcs and constraints (without values yet).
396    /// Net terminals become nodes in the graph.
397    /// Cell-internal arcs are derived from the `cell_delay_arcs`.
398    /// Inter-cell arcs (interconnects) are derived from the netlist of the top cell `top_ref`.
399    pub fn build_from_netlist<C, D>(
400        top_ref: &CellRef<N>,
401        delay_model: &D,
402        constraint_model: &C,
403    ) -> Result<Self, StaError>
404    where
405        D: CellDelayModel<N, Delay = T::Delay>,
406        C: CellConstraintModel<N, Constraint = T::Constraint>,
407    {
408        // Create empty timing graph.
409        let mut timing_graph = Self::new();
410
411        for p in top_ref.each_pin() {
412            timing_graph.create_pin(p);
413        }
414
415        // Create nodes for each pin instance.
416        for inst in top_ref.each_cell_instance() {
417            timing_graph.create_cell_instance(&inst, delay_model, constraint_model);
418        }
419
420        timing_graph.create_delay_edges_inter_cell(top_ref)?;
421        timing_graph.create_primary_input_delay_arcs(top_ref);
422
423        // Add the root node to the frontier.
424        // This makes sure that the first incremental signal propagation computes all signals.
425        timing_graph.frontier.insert(timing_graph.aat_source_node);
426
427        Ok(timing_graph)
428    }
429
430    /// Create graph nodes for each pin instance.
431    pub fn create_cell_instance<C, D>(
432        &mut self,
433        inst: &CellInstRef<N>,
434        delay_model: D,
435        constraint_model: C,
436    ) where
437        D: CellDelayModel<N, Delay = T::Delay>,
438        C: CellConstraintModel<N, Constraint = T::Constraint>,
439    {
440        for p in inst.each_pin_instance() {
441            let terminal = p.into_terminal();
442            self.create_terminal(terminal.id());
443        }
444
445        self.create_delay_edges_intra_cell(inst, delay_model);
446        self.create_constraint_edges(inst, constraint_model);
447    }
448
449    pub fn remove_cell_instance(&mut self, inst: &CellInstRef<N>) {
450        for p in inst.each_pin_instance() {
451            let terminal = p.into_terminal();
452            self.remove_terminal(terminal.id());
453        }
454    }
455
456    /// Remove all inter-cell arcs created by this net.
457    pub fn remove_net(&mut self, net: &NetRef<N>) -> Result<(), StaError> {
458        let terminals = net.each_terminal();
459        // Distinguish between sources (drivers) and sinks.
460        let (sources, sinks): (Vec<_>, Vec<_>) = terminals.into_iter().partition(|t| {
461            // Is source?
462
463            match t {
464                db::TerminalRef::Pin(p) => p.direction().is_input(),
465                db::TerminalRef::PinInst(p) => p.pin().direction().is_output(),
466            }
467        });
468
469        if !sources.is_empty() {
470            if sources.len() != 1 {
471                log::warn!(
472                    "Net must have 1 driver (has {}): {}",
473                    sources.len(),
474                    net.qname(PATH_SEPARATOR)
475                );
476                return Err(StaError::Other(format!(
477                    "Net must have 1 driver (has {}): {}",
478                    sources.len(),
479                    net.qname(PATH_SEPARATOR)
480                )));
481            }
482
483            let source = sources[0].id();
484            let source_ids = self.term2node[&source];
485
486            for sink in sinks {
487                let sink_ids = self.term2node[&sink.id()];
488                for source_id in source_ids {
489                    for sink_id in sink_ids {
490                        let edge = self.arc_graph.find_edge(source_id, sink_id);
491                        if let Some(edge) = edge {
492                            self.arc_graph.remove_edge(edge);
493                        }
494                    }
495                }
496            }
497        }
498
499        Ok(())
500    }
501
502    /// Disconnect the terminal from the old net and connect it to the new net (if it is `Some`).
503    pub fn connect_terminal(&mut self, t: db::TerminalRef<N>, net: Option<N::NetId>) {
504        let is_source = match &t {
505            db::TerminalRef::Pin(p) => p.direction().is_input(),
506            db::TerminalRef::PinInst(p) => p.pin().direction().is_output(),
507        };
508
509        // Get graph nodes of this terminal (one for rising and one for falling edges).
510        let nodes = *self
511            .term2node
512            .get(&t.id())
513            .expect("terminal is not in timing graph");
514
515        let dir = if is_source {
516            petgraph::Direction::Outgoing
517        } else {
518            petgraph::Direction::Incoming
519        };
520
521        // Disconnect first.
522        // Need to collect the edges because later we need mutable access to the graph.
523        // Edges adjacent to the two nodes (rise/fall) of the terminal.
524        let edges: SmallVec<[_; 8]> = nodes
525            .iter()
526            .flat_map(|node| self.arc_graph.edges_directed(*node, dir))
527            // Sanity check.
528            .inspect(|e| {
529                assert_eq!(
530                    e.weight().edge_type,
531                    GraphEdgeType::Delay,
532                    "should remove only interconnect delay arcs"
533                )
534            })
535            .map(|e| (e.source(), e.target(), e.id()))
536            .collect();
537
538        for (src, dest, edge_id) in edges {
539            debug_assert!(
540                // Sanity check: make sure we don't accidentially remove an arc inside a cell but a interconnect arc.
541                if is_source {
542                    nodes.contains(&src)
543                } else {
544                    nodes.contains(&dest)
545                },
546                "this is not an interconnect edge"
547            );
548            self.arc_graph.remove_edge(edge_id);
549            self.frontier.insert(src);
550            self.frontier.insert(dest);
551        }
552
553        // Eventually connect to new net.
554        self.connect_nodes_to_net(t, net, is_source, nodes);
555    }
556
557    fn connect_nodes_to_net(
558        &mut self,
559        t: TerminalRef<'_, N>,
560        net: Option<N::NetId>,
561        is_source: bool,
562        nodes: [NodeIndex; 2],
563    ) {
564        let netlist = t.base();
565        if let Some(net) = net {
566            let net = netlist.net_ref(&net);
567            // Connect
568            if is_source {
569                for sink in net.each_sink() {
570                    let sink_nodes = self.term2node[&sink.id()];
571                    // Create edges Rise->Rise and Fall->Fall
572                    for (node, sink_node) in nodes.into_iter().zip(sink_nodes) {
573                        self.arc_graph.update_edge(
574                            node,
575                            sink_node,
576                            EdgeData::new(GraphEdgeType::Delay),
577                        );
578                        self.frontier.insert(node);
579                        self.frontier.remove(&sink_node); // Not necessary but might save some memory.
580                    }
581                }
582            } else {
583                for source in net.each_driver() {
584                    let source_nodes = self.term2node[&source.id()];
585                    // Create edges Rise->Rise and Fall->Fall
586                    for (node, source_node) in nodes.into_iter().zip(source_nodes) {
587                        // TODO: Not all edges are necessary.
588                        // For example in case of a buffer, there's no edges from a rise-node to a fall-node.
589                        self.arc_graph.update_edge(
590                            source_node,
591                            node,
592                            EdgeData::new(GraphEdgeType::Delay),
593                        );
594                        self.frontier.insert(source_node);
595                        self.frontier.remove(&node); // Not necessary but might save some memory.
596                    }
597                }
598            };
599        }
600    }
601
602    /// Create graph nodes which represent a terminal/pin in the netlist.
603    /// Creates two graph nodes. One for falling signals and one for rising signals.
604    pub fn create_terminal(&mut self, terminal_id: db::TerminalId<N>) {
605        debug_assert!(
606            !self.term2node.contains_key(&terminal_id),
607            "terminal is already present"
608        );
609
610        let node_types = [
611            GraphNodeType::TerminalRise(terminal_id.clone()),
612            GraphNodeType::TerminalFall(terminal_id.clone()),
613        ];
614        let node_ids = node_types.map(|t| self.arc_graph.add_node(NodeData::new(t)));
615        for node_id in &node_ids {
616            self.node2term.insert(*node_id, terminal_id.clone());
617        }
618        self.term2node.insert(terminal_id, node_ids);
619    }
620
621    // Create grpah nodes for an external pin.
622    fn create_pin(&mut self, pin: PinRef<N>) {
623        let terminal = pin.into_terminal();
624        self.create_terminal(terminal.id());
625    }
626
627    /// Remove a terminal from the timing graph.
628    /// Removes the two associated nodes for fall and rising signal edges.
629    fn remove_terminal(&mut self, terminal: db::TerminalId<N>) {
630        let nodes = self
631            .term2node
632            .remove(&terminal)
633            .expect("pin does not exist in timing graph");
634
635        for node in nodes {
636            self.remove_node(node);
637        }
638    }
639
640    /// Remove node/vertex from the timing graph.
641    fn remove_node(&mut self, node: NodeIndex) {
642        self.arc_graph.remove_node(node);
643        self.node2term.remove(&node).expect("node does not exist");
644    }
645
646    /// Remove a pin from the timing graph.
647    /// Removes the two associated nodes for fall and rising signal edges.
648    pub fn remove_pin(&mut self, pin: N::PinId) {
649        let terminal = db::TerminalId::PinId(pin);
650        self.remove_terminal(terminal)
651    }
652
653    /// Remove a pin from the timing graph.
654    /// Removes the two associated nodes for fall and rising signal edges.
655    pub fn remove_pin_instance(&mut self, pin: N::PinInstId) {
656        let terminal = db::TerminalId::PinInstId(pin);
657        self.remove_terminal(terminal)
658    }
659
660    fn create_delay_edges_inter_cell(&mut self, top_ref: &CellRef<N>) -> Result<(), StaError> {
661        let netlist = top_ref.base();
662        let top = top_ref.id();
663
664        // Create inter-cell arcs.
665        for net in netlist.each_internal_net(&top) {
666            let terminals = netlist.each_terminal_of_net_vec(&net);
667            // Distinguish between sources (drivers) and sinks.
668            let (sources, sinks): (Vec<_>, Vec<_>) = terminals.into_iter().partition(|t| {
669                // Is source?
670                match t {
671                    db::TerminalId::PinId(p) => netlist.pin_direction(p).is_input(),
672                    db::TerminalId::PinInstId(p) => {
673                        let pin = netlist.template_pin(p);
674                        netlist.pin_direction(&pin).is_output()
675                    }
676                }
677            });
678
679            if !sources.is_empty() {
680                if sources.len() != 1 {
681                    log::warn!(
682                        "Net must have 1 driver (has {}): {}",
683                        sources.len(),
684                        netlist.net_ref(&net).qname(PATH_SEPARATOR)
685                    );
686                    return Err(StaError::Other(format!(
687                        "Net must have 1 driver (has {}): {}",
688                        sources.len(),
689                        netlist.net_ref(&net).qname(PATH_SEPARATOR)
690                    )));
691                }
692
693                let source_terminal = &sources[0];
694                let source_ids = self.term2node[source_terminal];
695
696                for sink_terminal in sinks {
697                    let sink_ids = self.term2node[&sink_terminal];
698                    // Create two edges: source_fall -> sink_fall, source_rise -> sink_rise
699                    // Cross-over between fall & rise nodes is not possible for inter-cell arcs.
700                    for (source_id, sink_id) in source_ids.into_iter().zip(sink_ids) {
701                        self.arc_graph.update_edge(
702                            source_id,
703                            sink_id,
704                            EdgeData::new(GraphEdgeType::Delay),
705                        );
706                    }
707                }
708            }
709        }
710
711        Ok(())
712    }
713
714    fn pin_instance_to_graph_nodes(&self, pin_inst: &PinInstRef<N>) -> [NodeIndex; 2] {
715        let terminal = pin_inst.terminal_id();
716        *self
717            .term2node
718            .get(&terminal)
719            .expect("Pin node does not exist.")
720    }
721
722    fn create_delay_edges_intra_cell<D>(&mut self, inst: &CellInstRef<N>, delay_model: D)
723    where
724        D: CellDelayModel<N, Delay = T::Delay>,
725    {
726        let netlist = inst.base();
727
728        // Create intra-cell delay arcs.
729        // Store the intra-cell delay edges.
730
731        for delay_arc in delay_model.delay_arcs(netlist, &inst.template_id()) {
732            // Convert terminals of arc to graph node indices.
733            let [input_node, output_node] =
734                [delay_arc.input_pin, delay_arc.output_pin].map(|(p, edge)| {
735                    let nodes = self.pin_instance_to_graph_nodes(&inst.pin_instance(&p));
736                    // Select rise/fall nodes.
737                    nodes[edge as usize]
738                });
739
740            self.arc_graph.update_edge(
741                input_node,
742                output_node,
743                EdgeData::new(GraphEdgeType::Delay),
744            );
745        }
746    }
747
748    /// Create edges for all constraint arcs within the cell instance.
749    fn create_constraint_edges<C>(&mut self, inst: &CellInstRef<N>, constraint_model: C)
750    where
751        C: CellConstraintModel<N, Constraint = T::Constraint>,
752    {
753        let netlist = inst.base();
754
755        for constraint_arc in constraint_model.constraint_arcs(netlist, &inst.template_id()) {
756            // Convert terminals of arc to graph node indices.
757            let [related_node, constrained_node] =
758                [constraint_arc.related_pin, constraint_arc.constrained_pin].map(|(p, edge)| {
759                    let nodes = self.pin_instance_to_graph_nodes(&inst.pin_instance(&p));
760                    // Select rise/fall nodes.
761                    nodes[edge as usize]
762                });
763
764            self.arc_graph.update_edge(
765                related_node,
766                constrained_node,
767                EdgeData::new(GraphEdgeType::Constraint),
768            );
769
770            // Keep track of the constraint arc by adding an edge to the RAT source node.
771            self.arc_graph.update_edge(
772                self.rat_source_node,
773                constrained_node,
774                EdgeData::new(GraphEdgeType::Virtual),
775            );
776        }
777    }
778
779    /// Create a directed edge from the AAT source node the primary input nodes.
780    fn create_primary_input(&mut self, primary_input: db::TerminalId<N>) {
781        let t_ids = self.term2node[&primary_input];
782        // TODO: Take input delays into account here.
783        for t_id in t_ids {
784            self.arc_graph.update_edge(
785                self.aat_source_node,
786                t_id,
787                EdgeData::new(GraphEdgeType::Delay),
788            );
789        }
790    }
791
792    /// Create directed edges from the AAT source node all primary input nodes.
793    fn create_primary_input_delay_arcs(&mut self, top_cell: &CellRef<N>) {
794        // Create directed edges from the AAT source node to all primary input nodes.
795        for prim_input in get_primary_inputs(top_cell) {
796            self.create_primary_input(prim_input.into_terminal().into());
797        }
798    }
799
800    // Check that there are no cycles in the graph.
801    pub fn check_cycles(&self) -> Result<(), StaError> {
802        // Create the delay arc graph by filtering the graph edges (remove constraint arcs).
803        let delay_graph = petgraph::visit::EdgeFiltered::from_fn(&self.arc_graph, |edge_ref| {
804            edge_ref.weight().edge_type.is_delay_arc()
805        });
806
807        // Create the constraint arc graph by filtering the graph edges (remove delay arcs).
808        let constraint_graph =
809            petgraph::visit::EdgeFiltered::from_fn(&self.arc_graph, |edge_ref| {
810                edge_ref.weight().edge_type.is_constraint_arc()
811            });
812
813        let delay_is_cyclic = petgraph::algo::is_cyclic_directed(&delay_graph);
814        let constraint_is_cyclic = petgraph::algo::is_cyclic_directed(&constraint_graph);
815
816        if delay_is_cyclic {
817            log::error!("Cycle found in delay graph.")
818        }
819        if constraint_is_cyclic {
820            log::error!("Cycle found in constraint graph.")
821        }
822
823        if delay_is_cyclic {
824            Err(StaError::CombinationalCycle)
825        } else {
826            Ok(())
827        }
828    }
829}
830
831/// Get input pins of a cell.
832fn get_primary_inputs<'a, N: NetlistBase>(top_ref: &CellRef<'a, N>) -> Vec<PinRef<'a, N>> {
833    top_ref
834        .each_pin()
835        .filter(|p| p.direction().is_input())
836        .collect()
837}