文件
phonerail/kicad/scripts/verify_erc_disposition.py
T

130 行
7.3 KiB
Python

"""Verify the Hub ECO and produce a complete, explicit disposition of remaining ERC."""
import csv
import json
import re
import xml.etree.ElementTree as ET
import pcbnew as pcb
from check_hardware import Board
from hub_strap_checks import REFS, STRAPS, verify_hub_straps
from revise_hub_straps import OUT, NC, CLI, PCB
from schematic_maintenance import child, children, parse, properties
from unconnected_inventory import classify, inventory, write_inventory
def snapshot(path, omit=()):
root = ET.parse(path).getroot()
parts = {c.attrib['ref']: ET.tostring(c) for c in root.findall('components/comp') if c.attrib['ref'] not in omit}
nets = {}
for net in root.findall('nets/net'):
pins = {(p.attrib['ref'], p.attrib['pin']): dict(p.attrib)
for p in net.findall('node') if p.attrib['ref'] not in omit}
if pins:
nets[frozenset(pins)] = pins
return parts, nets
def main():
from check_power_paths import main as verify_power_paths
verify_power_paths()
rows, unconnected, boards = [], [], {}
after = OUT / 'verified'
after.mkdir(exist_ok=True)
for number in (1, 2):
b = Board(number)
b.compile(CLI, after)
added = REFS if number == 2 else set()
old_parts, old_nets = snapshot(OUT / 'before' / f'Board{number}.xml')
new_parts, new_nets = snapshot(after / f'Board{number}.xml', added)
assert old_parts == new_parts, 'Unrelated component metadata changed'
assert old_nets.keys() == new_nets.keys(), 'Unapproved connectivity change'
assert set(b.parts) - set(old_parts) == added
expected_nc = {(ref, pin) for ref, pins in NC[number].items() for pin in pins}
changed = set()
for members, pins in old_nets.items():
for endpoint, attrs in pins.items():
expected = dict(attrs)
if endpoint in expected_nc:
assert len(members) == 1 and 'no_connect' not in attrs['pintype']
expected['pintype'] += '+no_connect'
assert expected == new_nets[members][endpoint], endpoint
if attrs != expected:
changed.add(endpoint)
assert changed == expected_nc
if number == 2:
def check(label, good):
assert good, label
verify_hub_straps(b, check)
current_pcb = pcb.LoadBoard(str(PCB))
previous_pcb = pcb.LoadBoard(str(OUT / 'before' / PCB.name))
footprints = {f.GetReference(): f for f in current_pcb.GetFootprints()}
changed_pins = {str(pin) for pin in STRAPS.values()}
for old_fp in previous_pcb.GetFootprints():
ref = old_fp.GetReference()
new_fp = footprints[ref]
assert old_fp.GetPosition() == new_fp.GetPosition() and old_fp.GetLayer() == new_fp.GetLayer(), ref
assert old_fp.GetOrientationDegrees() == new_fp.GetOrientationDegrees(), ref
new_pads = {p.GetNumber(): p for p in new_fp.Pads()}
for old_pad in old_fp.Pads():
new_pad = new_pads[old_pad.GetNumber()]
assert old_pad.GetPosition() == new_pad.GetPosition(), ref
if ref != 'U32' or old_pad.GetNumber() not in changed_pins:
assert old_pad.GetNetname() == new_pad.GetNetname(), (ref, old_pad.GetNumber())
report = json.loads((after / f'Board{number}-erc.json').read_text())
before = json.loads((OUT / 'before' / f'Board{number}-erc.json').read_text())
for key in ('ignored_checks', 'included_severities'):
assert report[key] == before[key]
for sheet in report['sheets']:
for violation in sheet['violations']:
description = violation['items'][0]['description']
match = re.search(r'^Symbol (\S+) .*? (\S+) \[', description)
assert match, description
ref, pin = match.groups()
kind = violation['type']
net = b.pins.get((ref, pin), '')
if ref.startswith('#PWR'):
path = b.path.parent / (sheet['path'].strip('/') + '.kicad_sch')
instance = next(s for s in children(parse(path.read_text(encoding='utf-8')), 'symbol')
if properties(s)['Reference'][2] == ref)
net = properties(instance)['Value'][2]
status, blocking = 'REVIEW_REQUIRED', True
category, action = classify(number, ref, pin)
evidence = ''
if kind == 'power_pin_not_driven':
category = 'supply_model'
status, blocking = 'TEMPORARILY_ACCEPTED_FOR_ROUTING', False
evidence = 'out/power-path-review/verification.json'
action = 'Topology traced; retain ERC until source operation/ratings are reviewed, then add scoped PWR_FLAG'
elif category in ('hub_port_power', 'hub_overcurrent'):
category = 'hub_power_architecture'
evidence = '../五路直供电源板设计.md sections 15.2,21.6; USB2517.pdf pages 10,12'
action = 'Intentional external MCU control; verify 5V short-circuit protection and USB port power/OC reporting before release'
elif category == 'unused_dac_channel':
evidence = 'DAC63204.pdf pin table and power-down/NVM sections; ../firmware/components/drv/dac63204.c'
action = 'Runtime keeps unused channels Hi-Z; verify manufactured NVM image and recall/fault state before NC'
elif category == 'mcu_recovery':
action = 'Add accessible IO0/UART recovery test points; current schematic explicitly says they are not fitted'
rows.append({'board': number, 'sheet': sheet['path'], 'reference': ref, 'pin': pin,
'erc_type': kind, 'severity': violation['severity'], 'net': net,
'category': category, 'disposition': status,
'temporary_routing_acceptance': not blocking,
'release_blocking': True, 'evidence': evidence, 'next_action': action})
unconnected.extend(inventory(b))
boards[number] = {'original_components_unchanged': len(old_parts), 'added_components': sorted(added),
'original_endpoint_groups_preserved': len(new_nets), 'approved_nc': sorted(changed), 'erc': b.erc}
with (OUT / 'erc-disposition.csv').open('w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
(OUT / 'erc-disposition.json').write_text(json.dumps(rows, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
result = {'status': 'ECO_VERIFIED_NOT_RELEASED', 'boards': boards,
'remaining_erc': len(rows), 'temporary_routing_acceptances': sum(r['temporary_routing_acceptance'] for r in rows),
'release_review_required': sum(r['release_blocking'] for r in rows),
'unconnected': write_inventory(OUT, unconnected)}
(OUT / 'verification.json').write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(result))
if __name__ == '__main__':
main()