This note demonstrates a small connectivity tester / check nets program for KiCad. This program allows doing checks like: Is the U1:8 pin connected to GND?

Sometimes visual errors can creep in the schematic (and the PCB subsequently). This connectivity tester allows expressing the same connections in a non-visual way (with a different probability of making errors).

The ERC and DRC checks in KiCad work great but this non-visual test assertions provide another level of sanity checking.

These test assertions can be run automatically and more importantly continuously to ensure that the circuit correctness is still fine.

Code:

#!/usr/bin/env python3
"""
check_nets_pcbnew.py — Assert footprint pad → net mapping in a KiCad .kicad_pcb using pcbnew.

Usage:
  python check_nets_pcbnew.py /path/to/board.kicad_pcb \
    --case U1:11=GND --case J1:2=+5V

Tip:
  If 'import pcbnew' fails, run this with KiCad's bundled Python or add pcbnew to PYTHONPATH.
"""

import sys
import argparse

try:
    import pcbnew
except Exception as e:
    print("ERROR: Could not import pcbnew. Run from KiCad’s Python or ensure pcbnew is on PYTHONPATH.")
    print(e)
    sys.exit(2)


def parse_case(expr: str):
    """
    'U1:11=GND' -> (ref, pad, net)
    """
    try:
        left, net = expr.split("=")
        ref, pad = left.split(":")
        return ref.strip(), pad.strip(), net.strip()
    except Exception:
        raise ValueError(f"Bad --case format: '{expr}'. Use REF:PAD=NET (e.g. U1:11=GND).")


def find_footprint(board, ref: str):
    # Robust across KiCad versions: iterate footprints and match reference
    for fp in board.GetFootprints():
        if fp.GetReference() == ref:
            return fp
    return None


def get_pad(fp, pad_number: str):
    # Handles both string/number pad names
    pad = fp.FindPadByNumber(str(pad_number))
    return pad


def pad_net_name(pad) -> str:
    net = pad.GetNet()
    if net:
        # Some versions include a leading slash; normalize by stripping spaces
        return net.GetNetname().strip()
    return ""


def main():
    ap = argparse.ArgumentParser(description="Assert pad → net mapping in a KiCad .kicad_pcb.")
    ap.add_argument("board", help="Path to .kicad_pcb")
    ap.add_argument("--case", action="append", default=[],
                    help="Assertion in the form REF:PAD=NET (e.g. U1:11=GND). May be used multiple times.")
    args = ap.parse_args()

    if not args.case:
        print("No --case given. Example: --case U1:11=GND --case J1:2=+5V")
        return 2

    board = pcbnew.LoadBoard(args.board)
    failures = []

    for expr in args.case:
        try:
            ref, padno, want_net = parse_case(expr)
        except ValueError as ve:
            print(str(ve))
            return 2

        fp = find_footprint(board, ref)
        if fp is None:
            failures.append(f"[MISS] Footprint {ref} not found")
            continue

        pad = get_pad(fp, padno)
        if pad is None:
            failures.append(f"[MISS] {ref} pad {padno} not found")
            continue

        got = pad_net_name(pad) or "<no-net>"
        if got != want_net:
            failures.append(f"[FAIL] {ref}:{padno} expected '{want_net}', got '{got}'")
        else:
            print(f"[PASS] {ref}:{padno} is on '{got}'")

    if failures:
        print("Connectivity check FAILED:")
        for line in failures:
            print("  " + line)
        return 1

    print("Connectivity check PASSED.")
    return 0


if __name__ == "__main__":
    sys.exit(main())

Usage:

$ python check_nets_pcbnew.py SDR-Board.kicad_pcb --case U4:11="Net-(CLK0-In)"
[PASS] U4:11 is on 'Net-(CLK0-In)'
Connectivity check PASSED.