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