Turns out it's pretty easy once you have a GAL16V8.
PRG and CHR bank registers can be handled by a bunch of 74*670s. A single *670 can give you four 8KB PRG banks out of a 128KB PRG ROM, or four 2K banks of a 32KB CHR ROM (or RAM). A pair can handle either 2MB of PRG or 512KB of CHR.
The *670 has three-state outputs, so some extra logic in the 16V8 can disable its outputs at some addresses and rely on pull-up resistors to create a fixed bank. If this replaces all of the highest bank, it frees up one bank register for PRG RAM at $6000-$7FFF.
One bit of each of the CHR registers can be stolen to control name table mirroring, reducing the maximum CHR size to 16KB with a single *670 or 256KB with a pair. If you've been working out how to wire this up, you might have noticed that pattern table banking would use PPU A12 and A11, but name table banking would need PPU A11 and A10. The 16V8 can solve this problem by generating (A12 AND NOT A13) OR (A10 AND A13) to control one of the *670 read address lines. (I considered four-screen mirroring instead, but then it wouldn't be compatible with the famiclones that rely on the connection between PPU /A13 and CIRAM /CE.)
Now for the fun part: the scanline counter.
Okay, you got me, this is actually more like Acclaim's MC-ACC instead of Nintendo's MMC3. Much like the MC-ACC scanline counter, this one depends on background patterns at $0000, sprite patterns at $1000, and 8x8 sprites. It probably also needs a glitch filter on PPU A12.
Programming it would involve writing one register to enable/acknowledge/reload the counter, and writing another register to disable and acknowledge the counter. Keeping the IRQ disabled after the write requires internal feedback in the 16V8, which seems like it would work okay:
Code: Select all
**** CUPL: ****
load = romsel & write & a14 & a13;
clear = (clear & !(romsel & write & a14 & a13)) # (romsel & write & a14 & !a13);
**** Compiled equations (according to jedutil): ****
/o12 = /i1 & /i2 & i3 & i4
o12.oe = vcc
/o13 = i1 & /o13 +
i2 & /o13 +
/i3 & /o13 +
/i4 & /o13 +
/i1 & /i2 & i3 & /i4
o13.oe = vcc
- PRG bank *670 /WR
- CHR bank *670 /WR
- PRG bank *670 /RD
- PRG RAM /CE
- CHR bank *670 address line
- scanline counter /MR
- scanline counter /PL
- /ROMSEL
- R/W
- CPU A14
- CPU A13
- M2
- PPU A13
- PPU A12
- PPU A10
Now all I have to do is build it... and then write a game that uses it...