162 lines
4.7 KiB
Python
Executable File
162 lines
4.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2019 Jon Evans <jon@craftyjon.com>
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
# This utility compares two netlists generated by Eeschema and reports on
|
|
# connectivity differences between them. This only is looking at connectivity!
|
|
# Things like the components section, the visual format of the file, and the
|
|
# sorting of entries is not compared.
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sexpdata
|
|
import sys
|
|
|
|
|
|
def extract_nets(sexpr):
|
|
# Starts with ( export (version N) (...) )
|
|
for idx, key in enumerate(sexpr):
|
|
# print("{}: {}".format(idx, key))
|
|
if isinstance(key, list):
|
|
if isinstance(key[0], sexpdata.Symbol) and key[0].value() == "nets":
|
|
return key[1:]
|
|
return None
|
|
|
|
|
|
def unpack(sexpr):
|
|
ret = {}
|
|
for net in sexpr:
|
|
code = net[1][1]
|
|
name = net[2][1]
|
|
if isinstance(name, sexpdata.Symbol):
|
|
name = name.value()
|
|
name = str(name)
|
|
|
|
members = []
|
|
if len(net) < 4:
|
|
continue
|
|
|
|
for node in net[3:]:
|
|
ref = node[1][1]
|
|
pin = node[2][1]
|
|
|
|
if isinstance(ref, sexpdata.Symbol):
|
|
ref = ref.value()
|
|
|
|
if isinstance(pin, sexpdata.Symbol):
|
|
pin = pin.value()
|
|
|
|
ref = str(ref)
|
|
pin = str(pin)
|
|
|
|
members.append((ref, pin))
|
|
|
|
members.sort()
|
|
ret[name] = members
|
|
|
|
return ret
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description='Compare KiCad netlists')
|
|
parser.add_argument('first_netlist')
|
|
parser.add_argument('second_netlist')
|
|
|
|
args = parser.parse_args()
|
|
|
|
fn_a = args.first_netlist
|
|
fn_b = args.second_netlist
|
|
|
|
with open(fn_a, 'r') as f:
|
|
a = sexpdata.load(f)
|
|
|
|
with open(fn_b, 'r') as f:
|
|
b = sexpdata.load(f)
|
|
|
|
# Extract the nets portion
|
|
a = extract_nets(a)
|
|
b = extract_nets(b)
|
|
|
|
if a is None:
|
|
print("Could not read nets from {}".format(fn_a))
|
|
sys.exit(1)
|
|
|
|
if b is None:
|
|
print("Could not read nets from {}".format(fn_b))
|
|
sys.exit(1)
|
|
|
|
nets_a = unpack(a)
|
|
nets_b = unpack(b)
|
|
|
|
sa = set(nets_a.keys())
|
|
sb = set(nets_b.keys())
|
|
|
|
only_a = sa - sb
|
|
only_b = sb - sa
|
|
both = sa & sb
|
|
|
|
if len(only_a) == len(only_b) == 0:
|
|
print("{} and {} are identical".format(fn_a, fn_b))
|
|
sys.exit(0)
|
|
|
|
print("A: {}\nB: {}".format(fn_a, fn_b))
|
|
|
|
changed_header = False
|
|
|
|
for net_name in sorted(both):
|
|
if nets_a[net_name] != nets_b[net_name]:
|
|
if not changed_header:
|
|
print("\nChanged nets:\n")
|
|
changed_header = True
|
|
|
|
print("{}: {} => {}".format(net_name, nets_a[net_name],
|
|
nets_b[net_name]))
|
|
|
|
discards_a = set()
|
|
discards_b = set()
|
|
|
|
renamed_header = False
|
|
|
|
for net_name in sorted(only_a):
|
|
for candidate in only_b:
|
|
if nets_a[net_name] == nets_b[candidate]:
|
|
if not renamed_header:
|
|
print("\nRenamed nets (no connection changes):\n")
|
|
renamed_header = True
|
|
|
|
print("{} => {}".format(net_name, candidate))
|
|
discards_a.add(net_name)
|
|
discards_b.add(candidate)
|
|
|
|
only_a.difference_update(discards_a)
|
|
only_b.difference_update(discards_b)
|
|
|
|
if len(only_a) > 0:
|
|
print("\nOnly in {}:\n".format(fn_a))
|
|
print('\n'.join(["{}: {}".format(el, nets_a[el])
|
|
for el in sorted(only_a)]))
|
|
|
|
if len(only_b) > 0:
|
|
print("\nOnly in {}:\n".format(fn_b))
|
|
print('\n'.join(["{}: {}".format(el, nets_b[el])
|
|
for el in sorted(only_b)]))
|