1use std::collections::{HashMap, HashSet};
7use std::fmt::Display;
8
9use diagnostics::{Diagnostic, IssueSet, Severity};
10
11use super::*;
12
13#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
15pub struct ValidatorIssue {
16 cause: Cause,
17 severity: Severity,
18}
19
20#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
22pub enum Cause {
23 DuplicateCellNames {
25 id1: CellId,
27 id2: CellId,
29 name: ArcStr,
31 },
32 DuplicateInstanceNames {
34 inst_name: ArcStr,
36 cell_id: CellId,
38 cell_name: ArcStr,
40 },
41 DuplicateSignalNames {
43 id1: SignalId,
45 id2: SignalId,
47 name: ArcStr,
49 cell_id: CellId,
51 cell_name: ArcStr,
53 },
54 ShortedPorts {
56 signal: SignalId,
58 name: ArcStr,
60 cell_id: CellId,
62 cell_name: ArcStr,
64 },
65 MissingSignal {
67 id: SignalId,
69 cell_id: CellId,
71 cell_name: ArcStr,
73 },
74 MissingChild {
76 child_id: ChildId,
78 parent_cell_id: CellId,
80 parent_cell_name: ArcStr,
82 instance_name: ArcStr,
84 },
85 UnconnectedPort {
87 child_cell_id: CellId,
89 child_cell_name: ArcStr,
91 port: ArcStr,
93 parent_cell_id: CellId,
95 parent_cell_name: ArcStr,
97 instance_name: ArcStr,
99 },
100 ExtraPort {
102 child_cell_id: CellId,
104 child_cell_name: ArcStr,
106 port: ArcStr,
108 parent_cell_id: CellId,
110 parent_cell_name: ArcStr,
112 instance_name: ArcStr,
114 },
115 IndexOutOfBounds {
117 idx: usize,
119 width: usize,
121 cell_id: CellId,
123 cell_name: ArcStr,
125 },
126 MissingIndex {
128 signal_name: ArcStr,
130 cell_id: CellId,
132 cell_name: ArcStr,
134 },
135 IndexedWire {
137 signal_name: ArcStr,
139 cell_id: CellId,
141 cell_name: ArcStr,
143 },
144 PortWidthMismatch {
146 expected_width: usize,
148 actual_width: usize,
150 instance_name: ArcStr,
152 port: ArcStr,
154 parent_cell_id: CellId,
156 parent_cell_name: ArcStr,
158 child_cell_id: CellId,
160 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 pub(crate) fn new(cause: Cause, severity: Severity) -> Self {
174 Self { cause, severity }
175 }
176
177 #[inline]
179 pub fn cause(&self) -> &Cause {
180 &self.cause
181 }
182
183 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 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 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 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 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}