From 40ad8e3b27dc26e56c0b372129276e78bb866999 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sun, 19 Jul 2026 13:57:44 -0500 Subject: [PATCH] GAL U7 decoded: full RIO memory map recovered from the fuse dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit galdecode.py applies the GAL20V8 complex-mode fuse geometry (tables from MAME jedutil.cpp) to GAL20v8a_5764.JED with pin names from schematic sheet 1. Result triple-checks: schematic net names, firmware $A0xx accesses, and the chip's UES signature — which VWE programmed as 'U7'. Memory map: $2000-$9FFF SRAM (E-qualified), $A000 display write port, $A010 pod-bus latch, $A020-$A03F HCTL-2016 counters (A3 byte select via NOT_A3), $C000-$FFFF EPROM (explains the FF-padded dump), with OE* = E&R/W as the shared read enable. Co-Authored-By: Claude Fable 5 --- README.md | 7 ++-- gal/README.md | 71 ++++++++++++++++++++++++++++++++++ gal/galdecode.py | 91 ++++++++++++++++++++++++++++++++++++++++++++ gal/u7-pin-names.txt | 23 +++++++++++ 4 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 gal/README.md create mode 100644 gal/galdecode.py create mode 100644 gal/u7-pin-names.txt diff --git a/README.md b/README.md index 50864f86..27cd5c5c 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,10 @@ hardware addressing is what the 0x00–0x6F logical input map in Fuse map read 2023-03-09 from **U7 on the 1407 board** (GAL20V8A-15LP, hand-labeled **"5764"** — which matches the JEDEC fuse checksum `*C5764`, the source of the file name). Per sheet 1 it is the CPU **memory decoder** -(drawn as a 20L8). Follow-up: run the JED through `jedutil`/`galette` to -recover the boolean equations and cross-check the decoded memory map against -the firmware disassembly in [`rio-firmware/`](../../rio-firmware/). +(drawn as a 20L8). **Decoded** — equations, the recovered memory map +(`$2000-$9FFF` SRAM, `$A000/$A010/$A020` I/O windows, `$C000-$FFFF` EPROM) +and the firmware cross-check are in [`gal/README.md`](gal/README.md); the +chip's programmed signature even reads `U7`. ## Schematic scan — [`schematics/Scans_018-014.pdf`](schematics/Scans_018-014.pdf) diff --git a/gal/README.md b/gal/README.md new file mode 100644 index 00000000..17f6e57e --- /dev/null +++ b/gal/README.md @@ -0,0 +1,71 @@ +# U7 GAL20V8A decode — the RIO memory map, recovered + +[`GAL20v8a_5764.JED`](GAL20v8a_5764.JED) is the fuse map read out of **U7** +on the RIO board (P/N 1407 Rev. 3) — the chip the schematic draws as a +"20L8" memory decoder. Decoding the fuses (see below for how) yields the +complete address decode, which cross-checks perfectly against both the +schematic net names and the firmware disassembly. + +Three provenance details line up: the chip's hand-written sticker reads +**"5764"** = the JED fuse checksum (`*C5764`); the fuse map is in **complex +(20L8-emulation) mode** exactly as the schematic symbol suggests; and the +programmed electronic signature (UES) literally reads **`U7`** — VWE +signed the chip with its own reference designator. + +## Recovered equations + +Inputs: `A[15:3]` (A3 arrives via the pin-16 feedback path), `E`, `R/W`. +All outputs are combinatorial, always enabled. + +``` +/ROM_SEL = A15 & A14 ; pin 15 +/RAM_SEL = /A15 & A14 & E | /A15 & A13 & E | A15 & /A14 & /A13 & E ; pin 19 +/PH_SEL = A15 & /A14 & A13 & /A12../A6 & A5 ; pin 20 +/BRD_SEL = A15 & /A14 & A13 & /A12../A6 & /A5 & A4 & E & /RW ; pin 18 +/DSP_SEL = A15 & /A14 & A13 & /A12../A6 & /A5 & /A4 & E & /RW ; pin 17 +/OE = E & RW ; pin 22 +NOT_A3 = /A3 ; pin 21 +``` + +(`/A12../A6` abbreviates the run `/A12 & /A11 & /A10 & /A9 & /A8 & /A7 & /A6`; +full machine output comes from `galdecode.py`, below.) + +## The memory map + +| Address | Select | Device | Notes | +|---------|--------|--------|-------| +| `$0000-$1FFF` | *(none)* | 68HC11 internal | RAM `$0000-$00FF`, registers `$1000-$103F` | +| `$2000-$9FFF` | `RAM_SEL*` | LH52B256 32K SRAM | E-qualified, read/write; exactly 32K | +| `$A000-$A00F` | `DSP_SEL*` | External-display write port | write-only (`E & /RW`) | +| `$A010-$A01F` | `BRD_SEL` | Pod-bus control latch (74HC574, sheet 3) | write-only (`E & /RW`) | +| `$A020-$A03F` | `PH_SEL*` | HCTL-2016 encoder counters ×5 | `A3` picks high/low count byte via `NOT_A3`, `A[2:0]` picks chip via the 74LS138s | +| `$C000-$FFFF` | `ROM_SEL*` | 27C512 EPROM | top 16K of the 64K device | + +`OE*` (`E & R/W`) is the shared read-side output enable for +EPROM/SRAM/counters; the write-only decodes double as write strobes. + +**Firmware cross-check** — [`RIOv4_2.disasm.asm`](../../../rio-firmware/RIOv4_2.disasm.asm) +touches precisely these windows and nothing else in `$A0xx`: digit writes to +`$A000-$A007`, pod-bus latch writes to `$A010`, and encoder reads at +`$A021/$A023/$A025/$A027/$A029` + `$A028/$A030-$A038` (five chips × two count +bytes). It also explains the EPROM dump being `FF` below `$C000`: with +`ROM_SEL* = A15·A14` and all 16 address lines wired, only the top quarter of +the 27C512 is ever addressed. + +## How the decode was done + +[`galdecode.py`](galdecode.py) parses the JED and applies the GAL20V8 +complex-mode fuse geometry — row/column-to-pin tables, product-term-disable +fuses, XOR polarity, SYN/AC0 mode bits — taken from MAME's `jedutil.cpp` +(the reference implementation for PLD fuse maps). Pin-to-net names come +from schematic sheet 1 (`RIO_1407`) and live in +[`u7-pin-names.txt`](u7-pin-names.txt). Reproduce with: + +```sh +py docs/hardware/gal/galdecode.py docs/hardware/gal/GAL20v8a_5764.JED docs/hardware/gal/u7-pin-names.txt +``` + +The JED itself was read from the chip on 2023-03-09 (header says an +autoelectric.cn / XGecu-style programmer). Note `*F0` in the header: unlisted +fuse rows default to 0, i.e. unused product terms are all-connected +(always-false) rather than blown — the decoder handles this. diff --git a/gal/galdecode.py b/gal/galdecode.py new file mode 100644 index 00000000..cba3afb4 --- /dev/null +++ b/gal/galdecode.py @@ -0,0 +1,91 @@ +"""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)") diff --git a/gal/u7-pin-names.txt b/gal/u7-pin-names.txt new file mode 100644 index 00000000..3ba5c110 --- /dev/null +++ b/gal/u7-pin-names.txt @@ -0,0 +1,23 @@ +# U7 (GAL20V8A, "20L8" on schematic RIO_1407 sheet 1) pin -> net name +1 A15 +2 A14 +3 A13 +4 A12 +5 A11 +6 A10 +7 A9 +8 A8 +9 A7 +10 A6 +11 A5 +13 A4 +14 E +23 RW +16 A3 +15 ROM_SEL +22 OE +21 NOT_A3 +20 PH_SEL +19 RAM_SEL +18 BRD_SEL +17 DSP_SEL