AnasSarkiz/Game-Boy-Enhance
This code defines a hardware schematic featuring a CPU with associated power, clock, and interface components, including specialized chips, crystals, connectors, and passive components, all structured for circuit design and simulation.
- Version
- 1.0.3
- License
- unset
- Stars
- 0
scripts/audit-s4-reference.py
"""Audit the pinned S4 reference; this is not an audit of a finished console.
Uses KiCad's exported XML rather than inferring connections from drawing positions.
Run after refreshing s4/reference/netlist.xml with KiCad's netlist exporter.
"""
import csv
import hashlib
import json
import xml.etree.ElementTree as ET
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
S4 = ROOT / "s4"
def write_csv(filename, rows):
with (S4 / "reports" / filename).open("w", newline="") as output:
writer = csv.DictWriter(output, fieldnames=list(rows[0]), lineterminator="\n")
writer.writeheader()
writer.writerows(rows)
def component_properties(component):
return {prop.get("name"): prop.get("value", "") for prop in component.findall("property")}
def main():
manifest = json.loads((S4 / "reference/manifest.json").read_text())
for filename, expected_digest in manifest["files"].items():
actual_digest = hashlib.sha256((S4 / "reference" / filename).read_bytes()).hexdigest()
if actual_digest != expected_digest:
raise ValueError(f"Pinned reference changed: {filename}")
netlist_path = S4 / "reference/netlist.xml"
netlist = ET.parse(netlist_path).getroot()
components = {component.get("ref"): component for component in netlist.findall("components/comp")}
pin_nets = defaultdict(set)
cpu_rows = []
for net in netlist.findall("nets/net"):
for node in net.findall("node"):
pin_nets[(node.get("ref"), node.get("pin"))].add(net.get("name"))
if node.get("ref") == "U3":
cpu_rows.append({
"physical_pin": node.get("pin"),
"reference_pin_function": node.get("pinfunction"),
"reference_net": net.get("name"),
"s4_datasheet_verified": False,
})
cpu_rows.sort(key=lambda row: int(row["physical_pin"]))
write_csv("reference-cpu-pins.csv", cpu_rows)
write_csv("reference-bom.csv", [{
"reference": reference,
"symbol_value": component.findtext("value"),
"manufacturer_part_number": component_properties(component).get("MPN", ""),
"jlcpcb_part_number": component_properties(component).get("LCSC", ""),
"reference_sheet": component.find("sheetpath").get("names"),
"import_status": "not_qualified",
} for reference, component in components.items()])
duplicate_pin_assignments = [{"reference": pin[0], "physical_pin": pin[1], "nets": sorted(nets)}
for pin, nets in pin_nets.items() if len(nets) != 1]
# Explicit physical supply pins transcribed from the reference and S3 pin table.
# S4 limits still require its own authoritative documentation.
supply_pins = {
"+0V9": [46, 51, 81, 116, 117],
"+1V5": [30, 48, 49],
"+1V8": [20, 26, 28, 50, 65, 89, 97, 107],
"+3V3": [29, 34, 66, 77, 83, 128],
"GND": [91, 129],
}
supply_pin_mismatches = [
{"physical_pin": pin, "expected_reference_net": rail,
"actual_nets": sorted(pin_nets[("U3", str(pin))])}
for rail, pins in supply_pins.items() for pin in pins
if pin_nets[("U3", str(pin))] != {rail}
]
invalid_components = [{"reference": reference, "symbol_value": component.findtext("value")}
for reference, component in components.items()
if component.findtext("value") in [None, "", "TODO"]]
erc = json.loads((S4 / "reports/reference-erc.json").read_text())
violations = [violation for sheet in erc["sheets"] for violation in sheet["violations"]]
feedback_outputs = []
for regulator, upper, lower in [("U1", "R2", "R3"), ("U2", "R5", "R7")]:
upper_ohms = float(components[upper].findtext("value").removesuffix("k")) * 1000
lower_ohms = float(components[lower].findtext("value").removesuffix("k")) * 1000
feedback_outputs.append({"reference": regulator, "upper_resistor_ohms": upper_ohms,
"lower_resistor_ohms": lower_ohms,
"nominal_output_volts": round(0.6 * (1 + upper_ohms / lower_ohms), 6),
"basis": "TI TLV62569 Rev C feedback equation; nominal only"})
report = {
"scope": "unmodified_trellis_reference_only",
"reference_commit": "db4fe71623c14bcea47d7457d0db4c99d3899124",
"netlist_sha256": hashlib.sha256(netlist_path.read_bytes()).hexdigest(),
"component_count": len(components),
"net_count_including_unconnected": len(netlist.findall("nets/net")),
"cpu_pin_count": len(cpu_rows),
"duplicate_pin_assignments": duplicate_pin_assignments,
"supply_pin_mismatches": supply_pin_mismatches,
"undefined_component_values": invalid_components,
"nominal_regulator_outputs": feedback_outputs,
"reference_erc": {
"severity_counts": dict(Counter(violation["severity"] for violation in violations)),
"type_counts": dict(Counter(violation["type"] for violation in violations)),
"ignored_checks": erc["ignored_checks"],
"limitation": "148 library-configuration warnings; this is not clean ERC or PCB DRC",
},
"cpu_identity": {"symbol_value": components["U3"].findtext("value"),
"requested_mpn": component_properties(components["U3"])["MPN"],
"jlcpcb_part_number": component_properties(components["U3"])["LCSC"]},
"storage_identity": {"symbol_value": components["U5"].findtext("value"),
"mpn": component_properties(components["U5"])["MPN"],
"datasheet": components["U5"].findtext("datasheet")},
"console_implemented": False,
"s4_placement_checked": False,
"s4_routing_checked": False,
"s4_copper_drc_passed": False,
"all_connections_datasheet_verified": False,
"prototype_tested": False,
}
(S4 / "reports/reference-audit.json").write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps({key: report[key] for key in ["component_count", "cpu_pin_count",
"duplicate_pin_assignments", "supply_pin_mismatches", "undefined_component_values"]}, indent=2))
# A completed extraction is not a passed electrical check.
if duplicate_pin_assignments or supply_pin_mismatches or invalid_components or any(
violation["severity"] == "error" for violation in violations
):
raise SystemExit(1)
if __name__ == "__main__":
main()