libreda-pnr 0.0.4

Algorithm interface definitions of the LibrEDA place-and-route framework.
Documentation
// 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::{NetlistBase, NetlistUtil, TerminalId, Direction};

/// 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))
    }

}