scir/
drivers.rs

1//! SCIR driver validation.
2//!
3//! Looks for issues such as multiply-driven nets and floating nets.
4
5use std::collections::HashMap;
6use std::fmt::{Display, Formatter};
7
8use diagnostics::{Diagnostic, IssueSet, Severity};
9
10use super::*;
11
12/// A single node in a SCIR circuit.
13#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
14pub struct Net {
15    /// The name of the cell containing this net.
16    cell_name: ArcStr,
17    /// The name of the signal.
18    signal_name: ArcStr,
19    /// The signal bit index, if the signal is a bus.
20    idx: Option<usize>,
21}
22
23impl Display for Net {
24    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}/{}", self.cell_name, self.signal_name)?;
26        if let Some(idx) = self.idx {
27            write!(f, "[{idx}]")?;
28        }
29        Ok(())
30    }
31}
32
33/// An issue identified during validation of an SCIR library.
34#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
35pub struct DriverIssue {
36    cause: Cause,
37    severity: Severity,
38    net: Net,
39}
40
41/// The state of a net.
42#[derive(Debug, Clone, Default)]
43struct NetState {
44    /// The number of drivers on this net.
45    ///
46    /// A module input port counts as a driver,
47    /// since an input port is presumably driven
48    /// by some other module.
49    drivers: usize,
50    /// The number of "readers" on this net.
51    ///
52    /// A module output port counts as a tap,
53    /// since an output port is presumably read
54    /// by some other module.
55    taps: usize,
56    /// The number of inouts connected to this net.
57    ///
58    /// A module inout port counts as a tap.
59    inouts: usize,
60}
61
62impl NetState {
63    /// Creates a new [`NetState`].
64    #[inline]
65    fn new() -> Self {
66        Self::default()
67    }
68
69    /// Returns the number of connections to the net.
70    fn degree(&self) -> usize {
71        self.drivers + self.taps + self.inouts
72    }
73
74    /// Returns the effective number of drivers of the net.
75    ///
76    /// Counts inouts as drivers.
77    fn eff_drivers(&self) -> usize {
78        self.inouts + self.drivers
79    }
80
81    /// Validates the number of drivers, taps, and inouts on the net.
82    fn validate(&self, net: Net, output: &mut IssueSet<DriverIssue>) {
83        if self.drivers > 1 {
84            output.add(DriverIssue::new_and_log(
85                Cause::MultipleDrivers,
86                net.clone(),
87                Severity::Info,
88            ));
89        }
90
91        if self.taps > 0 && self.inouts + self.drivers == 0 {
92            output.add(DriverIssue::new_and_log(
93                Cause::NoDrivers,
94                net.clone(),
95                Severity::Warning,
96            ));
97        }
98
99        if self.degree() == 0 {
100            output.add(DriverIssue::new_and_log(
101                Cause::Floating,
102                net.clone(),
103                Severity::Warning,
104            ));
105        }
106
107        if self.taps == 0 && self.eff_drivers() == 1 {
108            output.add(DriverIssue::new_and_log(
109                Cause::NotConnected,
110                net.clone(),
111                Severity::Info,
112            ));
113        }
114    }
115}
116
117/// The cause of a driver analysis error or warning.
118#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
119pub enum Cause {
120    /// A net that is driven but not tapped.
121    ///
122    /// Example: the output of an inverter is left unconnected.
123    NotConnected,
124    /// A net that is declared but not connected to anything.
125    Floating,
126    /// A net has multiple drivers.
127    ///
128    /// This may not be an issue in all contexts.
129    ///
130    /// Example: two inverters drive the same output net.
131    MultipleDrivers,
132    /// A net that is used, but has no drivers.
133    ///
134    /// Example: an inverter whose input port is not connected.
135    NoDrivers,
136}
137
138impl Diagnostic for DriverIssue {
139    fn severity(&self) -> Severity {
140        self.severity
141    }
142}
143
144impl DriverIssue {
145    /// Creates a new validator issue from the given cause and severity.
146    pub(crate) fn new(cause: Cause, net: Net, severity: Severity) -> Self {
147        Self {
148            cause,
149            net,
150            severity,
151        }
152    }
153
154    /// Gets the underlying cause of this issue.
155    #[inline]
156    pub fn cause(&self) -> &Cause {
157        &self.cause
158    }
159
160    /// Creates a new validator issue and logs it immediately.
161    ///
162    /// The log level will be selected according to the given severity.
163    pub(crate) fn new_and_log(cause: Cause, net: Net, severity: Severity) -> Self {
164        let result = Self::new(cause, net, severity);
165        match severity {
166            Severity::Info => tracing::event!(Level::INFO, issue = ?result.cause, "{}", result),
167            Severity::Warning => tracing::event!(Level::WARN, issue = ?result.cause, "{}", result),
168            Severity::Error => tracing::event!(Level::ERROR, issue = ?result.cause, "{}", result),
169        }
170        result
171    }
172}
173
174impl Display for DriverIssue {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        write!(f, "{}: {}", self.cause, self.net)
177    }
178}
179
180impl Display for Cause {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        match self {
183            Self::Floating => write!(f, "floating net"),
184            Self::MultipleDrivers => write!(f, "multiple drivers on the same net"),
185            Self::NoDrivers => write!(f, "net is used (i.e. read from), but has no drivers"),
186            Self::NotConnected => write!(f, "net is driven but never used elsewhere"),
187        }
188    }
189}
190
191impl<S: Schema + ?Sized> LibraryBuilder<S> {
192    /// Perform driver analysis on this library.
193    pub fn validate_drivers(&self) -> IssueSet<DriverIssue> {
194        let _guard = span!(Level::INFO, "performing driver analysis on SCIR Library").entered();
195        let mut issues = IssueSet::new();
196        self.validate_drivers_inner(&mut issues);
197        issues
198    }
199
200    fn validate_drivers_inner(&self, issues: &mut IssueSet<DriverIssue>) {
201        for &id in self.cells.keys() {
202            self.validate_cell_drivers(id, issues);
203        }
204    }
205
206    fn validate_cell_drivers(&self, id: CellId, issues: &mut IssueSet<DriverIssue>) {
207        let cell = self.cells.get(&id).unwrap();
208        let _guard =
209            span!(Level::INFO, "validating SCIR cell drivers", cell.id = %id, cell.name = %cell.name)
210                .entered();
211
212        let mut net_states: HashMap<SignalId, Vec<NetState>> =
213            HashMap::from_iter(cell.signals().map(|(id, info)| {
214                let len = info.width.unwrap_or(1);
215                (id, vec![NetState::new(); len])
216            }));
217
218        for port in cell.ports() {
219            for state in net_states.get_mut(&port.signal()).unwrap() {
220                match port.direction {
221                    Direction::Input => state.drivers += 1,
222                    Direction::Output => state.taps += 1,
223                    Direction::InOut => state.inouts += 1,
224                }
225            }
226        }
227
228        for (_, instance) in cell.instances.iter() {
229            analyze_instance(self, &mut net_states, instance);
230        }
231
232        for (sig, list) in net_states.iter() {
233            for (i, state) in list.iter().enumerate() {
234                let info = cell.signal(*sig);
235                state.validate(
236                    Net {
237                        cell_name: cell.name().clone(),
238                        signal_name: info.name.clone(),
239                        idx: info.width.map(|_| i),
240                    },
241                    issues,
242                );
243            }
244        }
245    }
246}
247
248fn analyze_instance<S: Schema + ?Sized>(
249    lib: &LibraryBuilder<S>,
250    net_states: &mut HashMap<SignalId, Vec<NetState>>,
251    inst: &Instance,
252) {
253    match inst.child() {
254        ChildId::Cell(id) => {
255            let cell = lib.cell(id);
256            for (port, conn) in inst.connections() {
257                let dir = cell.port(port).direction;
258                for part in conn.parts() {
259                    let states = net_states.get_mut(&part.signal()).unwrap();
260                    if let Some(range) = part.range() {
261                        for idx in range {
262                            update_net_state(&mut states[idx], dir);
263                        }
264                    } else {
265                        update_net_state(&mut states[0], dir);
266                    }
267                }
268            }
269        }
270        ChildId::Primitive(_) => {
271            // Primitives are opaque to SCIR, so we don't know primitive port directions.
272            // We treat all nets connected to primitives as having direction InOut.
273            let dir = Direction::InOut;
274            for conn in inst.connections().values() {
275                for part in conn.parts() {
276                    let states = net_states.get_mut(&part.signal()).unwrap();
277                    if let Some(range) = part.range() {
278                        for idx in range {
279                            update_net_state(&mut states[idx], dir);
280                        }
281                    } else {
282                        update_net_state(&mut states[0], dir);
283                    }
284                }
285            }
286        }
287    }
288}
289
290fn update_net_state(state: &mut NetState, dir: Direction) {
291    match dir {
292        Direction::Output => state.drivers += 1,
293        Direction::Input => state.taps += 1,
294        Direction::InOut => state.inouts += 1,
295    }
296}