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-core.py
"""Audit the connected stage against pinned XML, including pin/net partitions."""
import hashlib
import json
import math
import xml.etree.ElementTree as ET
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
S4 = ROOT / "s4"
POWER_INPUTS = {20, 26, 29, 34, 46, 48, 49, 50, 51, 65, 66, 77, 81, 83, 89, 97, 107, 116, 117, 128}
USB_CONTACTS = {"A1": ["5"], "B12": ["5"], "A12": ["7"], "B1": ["7"],
"A4": ["6"], "B9": ["6"], "A9": ["8"], "B4": ["8"],
"A5": ["15"], "B5": ["9"], "A6": ["13"], "B6": ["11"],
"A7": ["12"], "B7": ["14"], "S1": ["1", "2", "3", "4"]}
def reference_endpoints(net, expected_refs):
endpoints = set()
for node in net.findall("node"):
ref, pin = node.attrib["ref"], node.attrib["pin"]
if ref not in expected_refs:
continue
if ref == "J1":
endpoints.update((ref, mapped) for mapped in USB_CONTACTS[pin])
else:
if ref == "U3" and pin == "53":
pin = "39" # BOARD_ID_2: PD21 -> PE8, preserve LCD VSYNC.
elif ref == "U3" and pin == "52":
pin = "38" # BOARD_ID_3: PD22 -> PE9.
endpoints.add((ref, pin))
return endpoints
def main():
circuit_path = ROOT / "dist/s4/power-core/circuit.json"
circuit = json.loads(circuit_path.read_text())
manifest = json.loads((S4 / "generated/power-core.json").read_text())
components = {entry["source_component_id"]: entry for entry in circuit if entry["type"] == "source_component"}
expected_refs = {entry["reference"] for entry in manifest["components"]}
assert {entry["name"] for entry in components.values()} == expected_refs
ports = [entry for entry in circuit if entry["type"] == "source_port"]
partitions = {}
for port in ports:
key = port.get("subcircuit_connectivity_map_key")
if key:
partitions.setdefault(key, set()).add((components[port["source_component_id"]]["name"], str(port["pin_number"])))
reference_groups = {}
schematic = ET.parse(S4 / "reference/netlist.xml").getroot()
for net in schematic.findall("./nets/net"):
if net.attrib["name"].startswith("unconnected-"):
continue
endpoints = reference_endpoints(net, expected_refs)
if net.attrib["name"] == "+3V3":
endpoints.remove(("R11", "1"))
if net.attrib["name"] == "+1V8":
endpoints.add(("R11", "1"))
if len(endpoints) >= 2:
reference_groups[net.attrib["name"]] = endpoints
# Verify actual continuous nets through ST's two internally connected paths.
for cpu_net, connector_net in [("/Architecture/USB/USB0_DN", "Net-(J1-D--PadA7)"),
("/Architecture/USB/USB0_DP", "Net-(J1-D+-PadA6)")]:
reference_groups[cpu_net].update(reference_groups.pop(connector_net))
controls = json.loads((S4 / "controls.json").read_text())["buttons"]
required_game_inputs = {"UP", "DOWN", "LEFT", "RIGHT", "A", "B", "START", "SELECT", "L", "R"}
assert {button["function"] for button in controls} == required_game_inputs and len(controls) == 10
assert {button["cpu_pin"] for button in controls} == {44, 45, 35, 33, 41, 40, 37, 36, 32, 31}
for button in controls:
reference_groups["+3V3"].add((button["pullup"], "1"))
reference_groups["GND"].update([(button["switch"], "1"), (button["filter"], "2")])
reference_groups["BUTTON_" + button["function"] + "_N"] = {
("U3", str(button["cpu_pin"])), (button["switch"], "2"),
(button["pullup"], "2"), (button["filter"], "1")}
expected_partitions = list(map(frozenset, reference_groups.values()))
assert set(map(frozenset, partitions.values())) == set(expected_partitions), "Missing connection or unexpected net merge"
for selected in manifest["components"]:
component = next(entry for entry in components.values() if entry["name"] == selected["reference"])
assert component["manufacturer_part_number"] == selected["manufacturer_part_number"]
assert component["supplier_part_numbers"]["jlcpcb"] == [selected["jlcpcb_part_number"]]
cpu = next(entry for entry in components.values() if entry["name"] == "U3")
cpu_ports = {entry["pin_number"]: entry for entry in ports if entry["source_component_id"] == cpu["source_component_id"]}
assert set(cpu_ports) == set(range(1, 130))
assert {pin for pin, port in cpu_ports.items() if port.get("requires_power")} == POWER_INPUTS
assert {pin for pin, port in cpu_ports.items() if port.get("provides_power")} == {28, 30}
assert {pin for pin, port in cpu_ports.items() if port.get("requires_ground")} == {91, 129}
assert cpu_ports[106]["do_not_connect"] and not cpu_ports[106].get("subcircuit_connectivity_map_key")
for pin in POWER_INPUTS | {28, 30, 91, 129}:
assert cpu_ports[pin]["must_be_connected"] and cpu_ports[pin].get("subcircuit_connectivity_map_key")
by_ref_pin = {(components[port["source_component_id"]]["name"], port["pin_number"]): port for port in ports}
for ref, required_pins in {"U4": range(1, 7), "U5": range(1, 9), "U6": range(1, 6),
"J1": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]}.items():
for pin in required_pins:
port = by_ref_pin[(ref, pin)]
assert port["must_be_connected"] and port.get("subcircuit_connectivity_map_key"), f"Required interface pin missing: {ref}.{pin}"
for pin in [10, 16]:
port = by_ref_pin[("J1", pin)]
assert port["do_not_connect"] and not port.get("subcircuit_connectivity_map_key"), "USB SBU must stay unused in USB2-only port"
for pin in [52, 53, 62, 63]:
assert not cpu_ports[pin].get("subcircuit_connectivity_map_key"), "Future display pins conflict with straps/debug"
allocation = json.loads((S4 / "pin-allocation.json").read_text())["assignments"]
assert len({entry["cpu_pin"] for entry in allocation}) == len(allocation), "CPU pin allocated twice"
assert len({entry["signal"] for entry in allocation}) == len(allocation), "Signal allocated twice"
named_nets = {entry["name"]: entry for entry in circuit if entry["type"] == "source_net"}
for assigned in allocation:
port = cpu_ports[assigned["cpu_pin"]]
assert assigned["gpio"] in port["port_hints"], "Allocation disagrees with physical CPU pin label"
if assigned["status"] == "reserved_unwired":
assert not port.get("subcircuit_connectivity_map_key"), f"Reserved pin is already occupied: {assigned}"
else:
assert assigned["status"] == "connected"
assert port["subcircuit_connectivity_map_key"] == named_nets[assigned["signal"]]["subcircuit_connectivity_map_key"]
pcb_components = [entry for entry in circuit if entry["type"] == "pcb_component"]
assert len(pcb_components) == len(components) and all(not entry.get("do_not_place") for entry in pcb_components)
courtyards = [entry for entry in circuit if entry["type"] == "pcb_courtyard_outline"]
assert {entry["pcb_component_id"] for entry in courtyards} == {entry["pcb_component_id"] for entry in pcb_components}
boxes = []
for courtyard in courtyards:
outline = courtyard["outline"]
assert len(outline) == 5 and outline[0] == outline[-1]
boxes.append((min(p["x"] for p in outline), max(p["x"] for p in outline), min(p["y"] for p in outline), max(p["y"] for p in outline)))
clearances = []
for index, first in enumerate(boxes):
for second in boxes[index + 1:]:
dx = max(first[0] - second[1], second[0] - first[1])
dy = max(first[2] - second[3], second[2] - first[3])
assert dx > 0 or dy > 0, "Overlapping imported courtyards"
clearances.append(math.hypot(max(dx, 0), max(dy, 0)))
assert min(clearances) >= 0.1, "Less than 0.1mm spacing between supplier courtyards"
diagnostics = [entry for entry in circuit if entry["type"].endswith(("_error", "_warning"))]
assert not any(entry["type"] in ["pcb_trace", "pcb_via"] for entry in circuit), "Routing must remain disabled"
assert len([entry for entry in circuit if entry["type"] == "schematic_sheet"]) == 6
report = {"scope": "connected_power_cpu_usb_storage_recovery_controls_stage_only", "component_count": len(components),
"courtyard_count": len(courtyards), "minimum_courtyard_gap_mm": round(min(clearances), 6),
"net_partitions_checked": len(expected_partitions),
"reference_net_partitions_checked": len(expected_partitions) - len(controls),
"control_net_partitions_checked": len(controls),
"connected_endpoints_checked": sum(map(len, expected_partitions)),
"populated_game_inputs": sorted(required_game_inputs),
"allocated_cpu_signal_pins_checked": len(allocation),
"cpu_supply_inputs_required_and_connected": sorted(POWER_INPUTS),
"cpu_ldo_outputs_required_and_connected": [28, 30], "cpu_ground_pins_required_and_connected": [91, 129],
"reference_deviations": manifest["deviations"],
"remaining_reference_components": manifest["unimplemented_reference_components"],
"routing_disabled": True, "routed_copper_drc_performed": False, "full_console": False,
"diagnostics": [{"type": entry["type"], "message": entry["message"]} for entry in diagnostics],
"compiled_circuit_sha256": hashlib.sha256(circuit_path.read_bytes()).hexdigest()}
(S4 / "reports/power-core-audit.json").write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps(report, indent=2))
assert not diagnostics, "Stage still has emitted DRC errors or warnings"
if __name__ == "__main__":
main()