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
// Copyright (c) 2020-2021 Thomas Kramer.
// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
//! Provides sanity checks for netlists such as finding driving conflicts or non-driven nets.
use crate::db::{Direction, NetlistBase, NetlistUtil, TerminalId};
/// For each net in the `cell` check that there's exactly one cell driving it.
/// The check is done only based on the pin direction as annotated in the netlist.
/// The internals of child cells are ignored and hence the check is not perfect: If a child cell
/// has an inout pin then it could be resolved by looking at the internals.
///
/// Errors are logged with `log::error!()`.
///
/// Return `Ok` when no problem was found,
/// return `Err((drive_conflicts, nets_without_drivers))` when a problem was found.
/// Where `drive_conflicts` is a list of the form `[(net, [driver1 terminal, driver2 terminal, ...], ...]`.
/// `nets_without_drivers` is a list of nets without driver.
pub fn check_drivers_in_cell<N: NetlistBase>(
netlist: &N,
cell: &N::CellId,
) -> Result<(), (Vec<(N::NetId, Vec<TerminalId<N>>)>, Vec<N::NetId>)> {
let cell_name = netlist.cell_name(cell);
log::debug!(
"Check for drive conflicts and nets without driver in cell '{}'.",
cell_name
);
let mut drive_conflicts = vec![];
let mut nets_without_driver = vec![];
let mut unconnected_nets = vec![]; // Nets without any terminals attached.
for net in netlist.each_internal_net(cell) {
let net_name = netlist.net_name(&net);
log::debug!("Checking net '{:?}'", net_name);
let mut drivers = vec![];
let mut inouts = vec![];
for t in netlist.each_terminal_of_net(&net) {
// A pin of the parent cell is considered to be a source when it's direction is 'INPUT',
// however a pin instance that connects to a child instance is considered a sink when it's direction is 'INPUT'.
match &t {
TerminalId::PinId(p) => match netlist.pin_direction(p) {
Direction::Input => drivers.push(t),
Direction::InOut => inouts.push(t),
_ => {}
},
TerminalId::PinInstId(p) => match netlist.pin_direction(&netlist.template_pin(p)) {
Direction::Output => drivers.push(t),
Direction::InOut => inouts.push(t),
_ => {}
},
}
}
log::debug!("Number of drivers: {}", drivers.len());
log::debug!("Number of inouts: {}", inouts.len());
if drivers.is_empty() {
if netlist.num_net_terminals(&net) == 0 {
if !netlist.is_constant_net(&net) {
// Constant nets are ignored for this warning.
log::warn!("Unconnected net '{:?}' in cell '{}'.", net_name, cell_name);
unconnected_nets.push(net);
}
} else {
log::error!(
"No driver found on net '{:?}' in cell '{}'.",
net_name,
cell_name
);
if netlist.is_constant_net(&net) {
log::warn!(
"Constant net '{:?}' in cell '{}' should be connected to a tie cell.",
net_name,
cell_name
);
}
nets_without_driver.push(net);
}
} else if drivers.len() > 1 {
log::error!(
"Drive conflict. {} drivers found on net '{:?}' in cell '{}'.",
drivers.len(),
net_name,
cell_name
);
drive_conflicts.push((net, drivers));
}
}
if drive_conflicts.is_empty() && nets_without_driver.is_empty() {
Ok(())
} else {
Err((drive_conflicts, nets_without_driver))
}
}