GAL U7 decoded: full RIO memory map recovered from the fuse dump
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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)")
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user