"""Decode a GAL20V8 JED (complex mode) into boolean equations.

Fuse-map semantics taken from MAME jedutil.cpp (config_gal20v8_pins,
get_pin_fuse_state, is_gal20v8_product_term_enabled,
does_output_enable_fuse_row_allow_output).
"""
import re
import sys

JEDPATH = sys.argv[1] if len(sys.argv) > 1 else r"C:\VWE\riojoy\docs\hardware\gal\GAL20v8a_5764.JED"

QF = 2706
fuses = [0] * QF  # *F0 default
text = open(JEDPATH).read()
for m in re.finditer(r"\*L(\d+)\s+([01]+)", text):
    start = int(m.group(1))
    for i, ch in enumerate(m.group(2)):
        fuses[start + i] = int(ch)

SYN, AC0 = fuses[2704], fuses[2705]
mode = {(1, 1): "complex", (1, 0): "simple", (0, 1): "registered", (0, 0): "registered"}[(SYN, AC0)]
print(f"SYN={SYN} AC0={AC0} -> {mode} mode")
assert mode == "complex", "decoder only handles complex mode"

# UES signature 2568-2631, 8 bytes MSB-first
ues = bytes(
    int("".join(map(str, fuses[2568 + 8 * i : 2576 + 8 * i])), 2) for i in range(8)
)
print("UES signature:", ues, "->", ues.decode("ascii", "replace"))

# complex-mode column map: pin -> (lowfusecolumn, highfusecolumn)
COLS = {1: (3, 2), 2: (1, 0), 3: (5, 4), 4: (9, 8), 5: (13, 12), 6: (17, 16),
        7: (21, 20), 8: (25, 24), 9: (29, 28), 10: (33, 32), 11: (37, 36),
        13: (39, 38), 14: (35, 34), 16: (31, 30), 17: (27, 26), 18: (23, 22),
        19: (19, 18), 20: (15, 14), 21: (11, 10), 23: (7, 6)}
# complex-mode rows: pin -> (OE row fuse, first PT fuse, last PT fuse)
ROWS = {22: (0, 40, 280), 21: (320, 360, 600), 20: (640, 680, 920),
        19: (960, 1000, 1240), 18: (1280, 1320, 1560), 17: (1600, 1640, 1880),
        16: (1920, 1960, 2200), 15: (2240, 2280, 2520)}
XOR = {15: 2567, 16: 2566, 17: 2565, 18: 2564, 19: 2563, 20: 2562, 21: 2561, 22: 2560}

# Optional net names for pins, filled after reading the schematic:
NAMES = {}
if len(sys.argv) > 2:
    for kv in open(sys.argv[2]):
        kv = kv.strip()
        if kv and not kv.startswith("#"):
            p, n = kv.split(None, 1)
            NAMES[int(p)] = n

def name(pin):
    return NAMES.get(pin, f"pin{pin}")

def pt_enabled(fuserow):
    return fuses[2640 + fuserow // 40] == 1

def term(fuserow):
    """Return list of literals, or None if term is always-false, '1' if always-true."""
    lits = []
    for pin in sorted(COLS):
        lo, hi = COLS[pin]
        l, h = fuses[fuserow + lo], fuses[fuserow + hi]
        if l == 0 and h == 1:
            lits.append("/" + name(pin))
        elif l == 1 and h == 0:
            lits.append(name(pin))
        elif l == 0 and h == 0:
            return None  # both intact -> always false
    return lits if lits else ["1"]

for pin in sorted(ROWS, reverse=True):
    oerow, first, last = ROWS[pin]
    oe = term(oerow) if pt_enabled(oerow) else None
    if oe is None:
        print(f"\npin {pin} ({name(pin)}): OE never true -> used as INPUT")
        continue
    pol = "active-HIGH" if fuses[XOR[pin]] else "active-LOW"
    pts = []
    for f in range(first, last + 40, 40):
        if not pt_enabled(f):
            continue
        t = term(f)
        if t is not None:
            pts.append(" & ".join(t))
    lhs = name(pin) if fuses[XOR[pin]] else "/" + name(pin)
    oestr = " & ".join(oe)
    print(f"\npin {pin} ({name(pin)}): {pol}, OE = {oestr}")
    if pts:
        print(f"  {lhs} = " + ("\n" + " " * (len(lhs) + 5) + "| ").join(pts))
    else:
        print(f"  {lhs} = 0  (no active product terms)")
