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.