scir/
validation.rs

1//! SCIR validation utilities.
2//!
3//! This module provides helpers for ensuring that SCIR libraries
4//! and cells are valid.
5
6use std::collections::{HashMap, HashSet};
7use std::fmt::Display;
8
9use diagnostics::{Diagnostic, IssueSet, Severity};
10
11use super::*;
12
13/// An issue identified during validation of an SCIR library.
14#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
15pub struct ValidatorIssue {
16    cause: Cause,
17    severity: Severity,
18}
19
20/// The cause of a SCIR error or warning.
21#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
22pub enum Cause {
23    /// Two or more cells have the same name.
24    DuplicateCellNames {
25        /// The ID of the first cell.
26        id1: CellId,
27        /// The ID of the second cell.
28        id2: CellId,
29        /// The conflicting name.
30        name: ArcStr,
31    },
32    /// Two instances in the same cell have the same name.
33    DuplicateInstanceNames {
34        /// The name of the instance.
35        inst_name: ArcStr,
36        /// The ID of the cell containing the offending instances.
37        cell_id: CellId,
38        /// The name of the cell.
39        cell_name: ArcStr,
40    },
41    /// Two signals in a cell have the same name.
42    DuplicateSignalNames {
43        /// The ID of the first signal.
44        id1: SignalId,
45        /// The ID of the second signal.
46        id2: SignalId,
47        /// The name of the signal.
48        name: ArcStr,
49        /// The ID of the offending cell.
50        cell_id: CellId,
51        /// The name of the offending cell.
52        cell_name: ArcStr,
53    },
54    /// A signal is listed as a port more than once.
55    ShortedPorts {
56        /// The ID of the offending signal.
57        signal: SignalId,
58        /// The name of the signal.
59        name: ArcStr,
60        /// The ID of the offending cell.
61        cell_id: CellId,
62        /// The name of the offending cell.
63        cell_name: ArcStr,
64    },
65    /// A signal identifier is used but not declared.
66    MissingSignal {
67        /// The ID of the signal.
68        id: SignalId,
69        /// The ID of the cell containing the missing signal.
70        cell_id: CellId,
71        /// The name of the cell containing the missing signal.
72        cell_name: ArcStr,
73    },
74    /// An instance in a parent cell references a child not present in the library.
75    MissingChild {
76        /// The ID of the child cell.
77        child_id: ChildId,
78        /// The ID of the parent cell.
79        parent_cell_id: CellId,
80        /// The name of the parent cell.
81        parent_cell_name: ArcStr,
82        /// The name of the offending instance.
83        instance_name: ArcStr,
84    },
85    /// An instance does not specify a connection to a port of its child cell.
86    UnconnectedPort {
87        /// The ID of the child cell.
88        child_cell_id: CellId,
89        /// The name of the child cell.
90        child_cell_name: ArcStr,
91        /// The name of the unconnected port.
92        port: ArcStr,
93        /// The ID of the cell containing the offending instance.
94        parent_cell_id: CellId,
95        /// The name of the cell containing the offending instance.
96        parent_cell_name: ArcStr,
97        /// The name of the instance in the parent cell.
98        instance_name: ArcStr,
99    },
100    /// An instance specifies a connection to a port that does not exist in the child cell.
101    ExtraPort {
102        /// The ID of the child cell.
103        child_cell_id: CellId,
104        /// The name of the child cell.
105        child_cell_name: ArcStr,
106        /// The name of the port the instance is trying to connect.
107        port: ArcStr,
108        /// The ID of the cell containing the offending instance.
109        parent_cell_id: CellId,
110        /// The name of the cell containing the offending instance.
111        parent_cell_name: ArcStr,
112        /// The name of the offending instance in the parent cell.
113        instance_name: ArcStr,
114    },
115    /// A bus index is out of bounds given the width of the bus.
116    IndexOutOfBounds {
117        /// The out-of-bounds index.
118        idx: usize,
119        /// The width of the signal.
120        width: usize,
121        /// The ID of the offending cell.
122        cell_id: CellId,
123        /// The name of the offending cell.
124        cell_name: ArcStr,
125    },
126    /// Used a bus without indexing into it.
127    MissingIndex {
128        /// The name of the signal.
129        signal_name: ArcStr,
130        /// The ID of the offending cell.
131        cell_id: CellId,
132        /// The name of the offending cell.
133        cell_name: ArcStr,
134    },
135    /// Attempted to index a single wire.
136    IndexedWire {
137        /// The name of the signal.
138        signal_name: ArcStr,
139        /// The ID of the offending cell.
140        cell_id: CellId,
141        /// The name of the offending cell.
142        cell_name: ArcStr,
143    },
144    /// An instance specified a connection of incorrect width.
145    PortWidthMismatch {
146        /// The expected width of the connection.
147        expected_width: usize,
148        /// The actual width of the connection.
149        actual_width: usize,
150        /// The name of the offending instance.
151        instance_name: ArcStr,
152        /// The name of the port with the invalid connection.
153        port: ArcStr,
154        /// The ID of the parent cell.
155        parent_cell_id: CellId,
156        /// The name of the parent cell.
157        parent_cell_name: ArcStr,
158        /// The ID of the child cell.
159        child_cell_id: CellId,
160        /// The name of the child cell.
161        child_cell_name: ArcStr,
162    },
163}
164
165impl Diagnostic for ValidatorIssue {
166    fn severity(&self) -> Severity {
167        self.severity
168    }
169}
170
171impl ValidatorIssue {
172    /// Creates a new validator issue from the given cause and severity.
173    pub(crate) fn new(cause: Cause, severity: Severity) -> Self {
174        Self { cause, severity }
175    }
176
177    /// Gets the underlying cause of this issue.
178    #[inline]
179    pub fn cause(&self) -> &Cause {
180        &self.cause
181    }
182
183    /// Creates a new validator issue and logs it immediately.
184    ///
185    /// The log level will be selected according to the given severity.
186    pub(crate) fn new_and_log(cause: Cause, severity: Severity) -> Self {
187        let result = Self::new(cause, severity);
188        match severity {
189            Severity::Info => tracing::event!(Level::INFO, issue = ?result.cause, "{}", result),
190            Severity::Warning => tracing::event!(Level::WARN, issue = ?result.cause, "{}", result),
191            Severity::Error => tracing::event!(Level::ERROR, issue = ?result.cause, "{}", result),
192        }
193        result
194    }
195}
196
197impl Display for ValidatorIssue {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        write!(f, "{}", self.cause)
200    }
201}
202
203impl Display for Cause {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        match self {
206            Self::DuplicateCellNames { name, .. } => write!(
207                f,
208                "duplicate cell names: found two or more cells named `{}`",
209                name
210            ),
211            Self::DuplicateInstanceNames {
212                inst_name,
213                cell_name,
214                ..
215            } => write!(
216                f,
217                "duplicate instance names: found two or more instances named `{}` in cell `{}`",
218                inst_name, cell_name,
219            ),
220            Self::DuplicateSignalNames {
221                name, cell_name, ..
222            } => write!(
223                f,
224                "duplicate signal names: found two or more signals named `{}` in cell `{}`",
225                name, cell_name
226            ),
227            Self::ShortedPorts {
228                name, cell_name, ..
229            } => write!(
230                f,
231                "shorted ports: port `{}` in cell `{}` is connected to a signal already used by another port",
232                name, cell_name
233            ),
234
235            Self::MissingSignal { id, cell_name, .. } => {
236                write!(f, "invalid signal ID {} in cell `{}`", id, cell_name)
237            }
238
239            Self::MissingChild {
240                child_id,
241                parent_cell_name,
242                instance_name,
243                ..
244            } => write!(
245                f,
246                "missing child cell: instance `{}` in cell `{}` references cell ID `{}`, but no cell with this ID was found in the library",
247                instance_name, parent_cell_name, child_id
248            ),
249
250            Self::UnconnectedPort {
251                child_cell_name,
252                port,
253                parent_cell_name,
254                instance_name,
255                ..
256            } => write!(
257                f,
258                "unconnected port: instance `{}` in cell `{}` does not specify a connection for port `{}` of cell `{}`",
259                instance_name, parent_cell_name, port, child_cell_name
260            ),
261
262            Self::ExtraPort {
263                child_cell_name,
264                port,
265                parent_cell_name,
266                instance_name,
267                ..
268            } => write!(
269                f,
270                "extra port: instance `{}` in cell `{}` specifies a connection for port `{}` of cell `{}`, but this cell has no such port",
271                instance_name, parent_cell_name, port, child_cell_name
272            ),
273
274            Self::IndexOutOfBounds {
275                idx,
276                width,
277                cell_name,
278                ..
279            } => write!(
280                f,
281                "index out of bounds: attempted to access index {} of signal with width {} in cell `{}`",
282                idx, width, cell_name
283            ),
284
285            Self::MissingIndex {
286                signal_name,
287                cell_name,
288                ..
289            } => write!(
290                f,
291                "missing index on use of bus signal `{}` in cell `{}`",
292                signal_name, cell_name
293            ),
294
295            Self::IndexedWire {
296                signal_name,
297                cell_name,
298                ..
299            } => write!(
300                f,
301                "attempted to index a single-bit wire: signal `{}` in cell `{}`",
302                signal_name, cell_name
303            ),
304
305            Self::PortWidthMismatch {
306                expected_width,
307                actual_width,
308                instance_name,
309                port,
310                parent_cell_name,
311                child_cell_name,
312                ..
313            } => write!(
314                f,
315                "mismatched port width: instance `{}` in cell `{}` specifies a connection to port `{}` of cell `{}` of width {}, but the expected width is {}",
316                instance_name,
317                parent_cell_name,
318                port,
319                child_cell_name,
320                actual_width,
321                expected_width
322            ),
323        }
324    }
325}
326
327impl<S: Schema + ?Sized> LibraryBuilder<S> {
328    /// Check whether this library is valid.
329    pub fn validate(&self) -> IssueSet<ValidatorIssue> {
330        let _guard = span!(Level::INFO, "validating SCIR Library").entered();
331        let mut issues = IssueSet::new();
332        self.validate1(&mut issues);
333
334        if issues.has_error() {
335            return issues;
336        }
337
338        self.validate2(&mut issues);
339        issues
340    }
341
342    fn validate1(&self, issues: &mut IssueSet<ValidatorIssue>) {
343        let _guard = span!(
344            Level::INFO,
345            "validation pass 1 (checking signal and port identifier validity)"
346        )
347        .entered();
348
349        let mut cell_names = HashMap::new();
350        for (id, cell) in self.cells.iter() {
351            self.validate_cell1(*id, issues);
352            if let Some(id1) = cell_names.insert(cell.name.clone(), id) {
353                let issue = ValidatorIssue::new_and_log(
354                    Cause::DuplicateCellNames {
355                        id1: *id1,
356                        id2: *id,
357                        name: cell.name.clone(),
358                    },
359                    Severity::Error,
360                );
361                issues.add(issue);
362            }
363        }
364    }
365
366    fn validate2(&self, issues: &mut IssueSet<ValidatorIssue>) {
367        let _guard = span!(
368            Level::INFO,
369            "validation pass 2 (checking connection validity)"
370        )
371        .entered();
372        for id in self.cells.keys().copied() {
373            self.validate_cell2(id, issues);
374        }
375    }
376
377    fn validate_cell1(&self, id: CellId, issues: &mut IssueSet<ValidatorIssue>) {
378        let cell = self.cells.get(&id).unwrap();
379        let _guard =
380            span!(Level::INFO, "validating SCIR cell (pass 1)", cell.id = %id, cell.name = %cell.name)
381                .entered();
382
383        let invalid_signal = |signal_id: SignalId| {
384            ValidatorIssue::new_and_log(
385                Cause::MissingSignal {
386                    id: signal_id,
387                    cell_id: id,
388                    cell_name: cell.name.clone(),
389                },
390                Severity::Error,
391            )
392        };
393
394        let mut inst_names = HashSet::new();
395        for (_id, instance) in cell.instances.iter() {
396            if inst_names.contains(&instance.name) {
397                issues.add(ValidatorIssue::new_and_log(
398                    Cause::DuplicateInstanceNames {
399                        inst_name: instance.name.clone(),
400                        cell_id: id,
401                        cell_name: cell.name.clone(),
402                    },
403                    Severity::Error,
404                ));
405            }
406            inst_names.insert(instance.name.clone());
407            for concat in instance.connections.values() {
408                for part in concat.parts() {
409                    let signal = match cell.signals.get(&part.signal()) {
410                        Some(signal) => signal,
411                        None => {
412                            issues.add(invalid_signal(part.signal()));
413                            continue;
414                        }
415                    };
416
417                    // check out of bounds indexing.
418                    match (signal.width, part.range()) {
419                        (Some(width), Some(range)) => {
420                            if range.end > width {
421                                issues.add(ValidatorIssue::new_and_log(
422                                    Cause::IndexOutOfBounds {
423                                        idx: range.end,
424                                        width,
425                                        cell_id: id,
426                                        cell_name: cell.name.clone(),
427                                    },
428                                    Severity::Error,
429                                ));
430                            }
431                        }
432                        (Some(_), None) => {
433                            issues.add(ValidatorIssue::new_and_log(
434                                Cause::MissingIndex {
435                                    signal_name: signal.name.clone(),
436                                    cell_id: id,
437                                    cell_name: cell.name.clone(),
438                                },
439                                Severity::Error,
440                            ));
441                        }
442                        (None, Some(_)) => {
443                            issues.add(ValidatorIssue::new_and_log(
444                                Cause::IndexedWire {
445                                    signal_name: signal.name.clone(),
446                                    cell_id: id,
447                                    cell_name: cell.name.clone(),
448                                },
449                                Severity::Error,
450                            ));
451                        }
452                        (None, None) => {}
453                    }
454                }
455            }
456        }
457
458        let mut port_signals = HashSet::with_capacity(cell.ports.len());
459        for port in cell.ports() {
460            if !cell.signals.contains_key(&port.signal) {
461                issues.add(invalid_signal(port.signal));
462                continue;
463            }
464
465            if !port_signals.insert(port.signal) {
466                let issue = ValidatorIssue::new_and_log(
467                    Cause::ShortedPorts {
468                        signal: port.signal,
469                        name: cell.signals.get(&port.signal).unwrap().name.clone(),
470                        cell_id: id,
471                        cell_name: cell.name.clone(),
472                    },
473                    Severity::Error,
474                );
475                issues.add(issue);
476            }
477        }
478
479        let mut signal_names = HashMap::new();
480        for (signal_id, signal) in cell.signals() {
481            if let Some(other) = signal_names.insert(&signal.name, signal_id) {
482                let issue = ValidatorIssue::new_and_log(
483                    Cause::DuplicateSignalNames {
484                        id1: signal_id,
485                        id2: other,
486                        name: signal.name.clone(),
487                        cell_id: id,
488                        cell_name: cell.name().clone(),
489                    },
490                    Severity::Error,
491                );
492                issues.add(issue);
493            }
494        }
495    }
496
497    fn validate_cell2(&self, id: CellId, issues: &mut IssueSet<ValidatorIssue>) {
498        let cell = self.cells.get(&id).unwrap();
499        let _guard =
500            span!(Level::INFO, "validating SCIR cell (pass 2)", cell.id = %id, cell.name = %cell.name)
501                .entered();
502
503        for (_id, instance) in cell.instances.iter() {
504            match instance.child {
505                ChildId::Cell(c) => {
506                    let child = match self.cells.get(&c) {
507                        Some(child) => child,
508                        None => {
509                            let issue = ValidatorIssue::new_and_log(
510                                Cause::MissingChild {
511                                    child_id: c.into(),
512                                    parent_cell_id: id,
513                                    parent_cell_name: cell.name.clone(),
514                                    instance_name: instance.name.clone(),
515                                },
516                                Severity::Error,
517                            );
518                            issues.add(issue);
519                            continue;
520                        }
521                    };
522                    let mut child_ports = HashSet::with_capacity(child.ports.len());
523
524                    // Check for missing ports
525                    for port in child.ports() {
526                        let name = &child.signals[&port.signal].name;
527                        child_ports.insert(name.clone());
528                        match instance.connections.get(name) {
529                            Some(conn) => {
530                                let expected_width = child.signals[&port.signal].width.unwrap_or(1);
531                                if conn.width() != expected_width {
532                                    let issue = ValidatorIssue::new_and_log(
533                                        Cause::PortWidthMismatch {
534                                            expected_width,
535                                            actual_width: conn.width(),
536                                            port: name.clone(),
537                                            instance_name: instance.name.clone(),
538                                            child_cell_id: instance.child.unwrap_cell(),
539                                            child_cell_name: child.name.clone(),
540                                            parent_cell_name: cell.name.clone(),
541                                            parent_cell_id: id,
542                                        },
543                                        Severity::Error,
544                                    );
545                                    issues.add(issue);
546                                }
547                            }
548                            None => {
549                                let issue = ValidatorIssue::new_and_log(
550                                    Cause::UnconnectedPort {
551                                        child_cell_id: instance.child.unwrap_cell(),
552                                        child_cell_name: child.name.clone(),
553                                        port: name.clone(),
554                                        parent_cell_name: cell.name.clone(),
555                                        parent_cell_id: id,
556                                        instance_name: instance.name.clone(),
557                                    },
558                                    Severity::Error,
559                                );
560                                issues.add(issue);
561                            }
562                        }
563                    }
564
565                    // Check for extra ports
566                    for conn in instance.connections.keys() {
567                        if !child_ports.contains(conn) {
568                            let issue = ValidatorIssue::new_and_log(
569                                Cause::ExtraPort {
570                                    child_cell_id: instance.child.unwrap_cell(),
571                                    child_cell_name: child.name.clone(),
572                                    port: conn.clone(),
573                                    parent_cell_name: cell.name.clone(),
574                                    parent_cell_id: id,
575                                    instance_name: instance.name.clone(),
576                                },
577                                Severity::Error,
578                            );
579                            issues.add(issue);
580                        }
581                    }
582                }
583                ChildId::Primitive(p) => {
584                    if self.try_primitive(p).is_none() {
585                        let issue = ValidatorIssue::new_and_log(
586                            Cause::MissingChild {
587                                child_id: p.into(),
588                                parent_cell_id: id,
589                                parent_cell_name: cell.name.clone(),
590                                instance_name: instance.name.clone(),
591                            },
592                            Severity::Error,
593                        );
594                        issues.add(issue);
595                    }
596                }
597            }
598        }
599    }
600}