feat(cartridge): add 512-byte and 8 KiB serial EEPROM saves

Select them with --save-type eeprom512 or eeprom8k. The part follows GBATEK's
protocol on Game Pak data bit 0: DMA3 sends one bit per halfword, '11' +
address + '0' requests a 64-bit block that reads back after four dummy bits,
and '10' + address + data + '0' writes one. A write reports busy for a nominal
108,368 cycles before the block is stored. 8 KiB parts decode the low 10
address bits. ROMs above 16 MiB map EEPROM only at 0x0dffff00..0x0dffffff.

Bits shift out only inside machine steps, so failed steps roll back. CPU
stores, byte/word accesses, CPU reads during a read-out, and malformed or
overlapping requests are diagnostics. Blocks are stored first-bit-as-MSB,
matching common .sav layouts, and work with save files and save states.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhfHfHEuzv2AUTd6erW1gU
This commit is contained in:
0xrsydn 2026-10-14 12:06:17 +07:00
commit 652ea397f2
15 changed files with 828 additions and 24 deletions

View file

@ -51,6 +51,8 @@ The repository is a Cargo workspace with three crates:
| `crates/gba-core/src/cartridge.rs` | Explicit GPIO/RTC selection, ROM read overlay, pin directions, caller-clock routing, and bounded diagnostics |
| `crates/gba-core/src/cartridge/flash.rs` | Explicit save devices, Macronix IDs/banks, delayed program/erase, bounded DQ7 polling, and staged mutations |
| `crates/gba-core/src/cartridge/sram.rs` | 32 KiB byte-wide SRAM, mirrored offsets, one staged byte per step, and its save-state section |
| `crates/gba-core/src/cartridge/eeprom.rs` | Serial EEPROM request decoding, 68-bit read-out, nominal write busy time, block commits, and its save-state section |
| `crates/gba-core/tests/eeprom.rs` | DMA3 EEPROM bitstreams, busy polling, 10-bit addressing, large-ROM window, diagnostics, and mid-read-out save states |
| `crates/gba-core/tests/rom_bus.rs` | Game Pak reads beyond the ROM chip in every window, trimmed-image gaps, strict host/fetch reads, and DMA sources |
| `crates/gba-core/tests/sram.rs` | SRAM selection, mirroring, byte stores/loads from ROM code, SWPB, width/DMA diagnostics, and save states |
| `crates/gba-core/tests/flash.rs` | Erased/supplied images, chip IDs, all bank addresses, invalid commands, reset limits, DMA rejection, and RTC independence |

View file

@ -35,8 +35,8 @@ CPU and DMA reads beyond the ROM chip, the image size rounded up to a power of t
Bytes between a trimmed image's end and that chip boundary are unknown and remain explicit memory errors, as do host reads and instruction fetches beyond the image.
All three Game Pak ROM windows use the same supplied bytes and existing wait-state rules.
Ordinary cartridge bytes remain read-only. CPU and DMA writes to the `0x08`–`0x0c` ROM windows pay their bus cost and are ignored, as on hardware.
Writes to the `0x0d` window remain `ReadOnly` diagnostics until EEPROM is modeled there. Host `Memory::write*` calls stay strict everywhere.
Optional `--save-type flash64` or `--save-type flash128` enables [nominal Flash programming, erase, and reads](saves.md); `--save-type sram` selects [32 KiB SRAM](saves.md#sram).
Writes to the `0x0d` window remain `ReadOnly` diagnostics unless [EEPROM](saves.md#eeprom) is selected there. Host `Memory::write*` calls stay strict everywhere.
Optional `--save-type flash64` or `--save-type flash128` enables [nominal Flash programming, erase, and reads](saves.md); `--save-type sram` selects [32 KiB SRAM](saves.md#sram), and `eeprom512` or `eeprom8k` selects [serial EEPROM](saves.md#eeprom).
Without `--save-file PATH`, changes remain volatile. With it, the frontend loads an exact-size image and persists changes on clean exit or when a ROM window ends on a diagnostic.
Missing images start erased. Existing images receive unique backups before replacement; incomplete Flash operations refuse persistence.
Read the [storage safeguards and limits](saves.md#desktop-persistence) before using a valuable save.

View file

@ -68,7 +68,7 @@ Writes into the mapped 16 KiB BIOS [complete without changing its image](bios.md
Source reads, channel data, address progression, timing, and completion IRQs still apply to these ignored writes.
DMA0–2 mask destination bit 27, so a programmed cartridge address can instead select the BIOS region.
Other accesses use the existing memory map, alignment checks, mirrors, and I/O write rules.
Other unmapped reads remain diagnostics, not general DMA open-bus behavior. Writes to the `0x08`–`0x0c` ROM windows and to unused I/O addresses are ignored; the `0x0d` window and `0x04000800` remain diagnostics.
Other unmapped reads remain diagnostics, not general DMA open-bus behavior. Writes to the `0x08`–`0x0c` ROM windows and to unused I/O addresses are ignored; the `0x0d` window (unless EEPROM is selected) and `0x04000800` remain diagnostics.
A failed unit changes no memory, device state, or clock. Earlier units remain committed; the failed unit remains pending.
Successful units update local IWRAM lanes for their actual IWRAM reads and writes, in that order.
No CPU continuation is required; DMA can establish lanes before the first CPU instruction.

View file

@ -1,15 +1,16 @@
# Cartridge Flash, SRAM, and save files
# Cartridge Flash, SRAM, EEPROM, and save files
The core supports explicit Macronix Flash selection, identification, bank selection, byte programming, and sector/chip erase.
Operations complete after nominal delays and expose bounded data polling. They do not complete immediately on the command write.
The desktop can load and persist raw images through explicit `--save-file PATH` selection.
Original ROM tests verify save/restart/load across processes. Saving in an external game remains unverified.
The core also supports explicit 32 KiB SRAM; see [SRAM](#sram).
EEPROM and other manufacturers' Flash protocols remain separate work.
The core also supports explicit 32 KiB SRAM and 512-byte or 8 KiB serial EEPROM; see [SRAM](#sram) and [EEPROM](#eeprom).
Other manufacturers' Flash protocols remain separate work.
## Selection and core API
Use `--save-type flash64`, `--save-type flash128`, or `--save-type sram` in terminal or window ROM modes.
Use `--save-type flash64`, `flash128`, `sram`, `eeprom512`, or `eeprom8k` in terminal or window ROM modes.
EEPROM games do not identify their part size, so the type must match the game (GBATEK: no autodetection mechanism exists).
Selection is independent of `--rtc`. Omitting the save type leaves save memory unmapped.
No filename, ROM signature, or first-write detection changes the selected device.
Without `--save-file`, the selected device starts erased (`0xff`) and all changes remain volatile.
@ -17,7 +18,7 @@ The ROM suite schema still has no save-selection or save-file field.
Core APIs on `Memory`:
- `set_save_device(SaveDevice)` selects `None`, `Flash64`, `Flash128`, or `Sram` and initializes an erased array.
- `set_save_device(SaveDevice)` selects `None`, `Flash64`, `Flash128`, `Sram`, `Eeprom512`, or `Eeprom8k` and initializes an erased array.
GPIO/RTC state is preserved.
- `load_save_image(&[u8])` accepts exactly the selected capacity. It resets commands, pending operations, ID mode, bank, and modification tracking.
Invalid size or missing hardware changes nothing.
@ -40,6 +41,25 @@ Halfword and word reads and writes, and DMA transfers, remain diagnostics until
SRAM shares the save window's WAITCNT wait states, save-image API, host persistence, and save states with Flash.
A save state records the selected device. Restoring it into a machine with another save device is rejected as a cartridge setup mismatch.
## EEPROM
`Eeprom512` and `Eeprom8k` follow GBATEK's serial protocol on Game Pak data bit 0, one bit per halfword.
With ROM up to 16 MiB the part answers anywhere in `0x0d000000..0x0dffffff`; above 16 MiB only `0x0dffff00..0x0dffffff`.
Elsewhere in that window, reads and writes behave as ordinary ROM.
- Read request: `11`, the block address (6 bits, or 14 bits with the low 10 decoded), then `0`.
The next 68 halfword reads return 4 zero bits, then the 64-bit block MSB first.
- Write request: `10`, the block address, 64 data bits MSB first, then `0`.
The part then reports busy (bit 0 clear) for a nominal 108,368 cycles before the block is stored.
- Idle and ready status reads return 1. Bits 1–15 read as zero; hardware drives only bit 0.
Block bytes are stored in transfer order: the first data bit is byte 0, bit 7, matching common `.sav` layouts.
Bitstreams must come from DMA (or host setup writes). GBATEK states manual `STRH` transfers do not work, so CPU stores are diagnostics.
A CPU `LDRH` polls status; a CPU read during a data read-out, byte or word accesses, a missing leading `1`,
a missing trailing `0`, a request while busy, and a request during a read-out are all diagnostics.
Bits shift out only inside machine steps, so a failed step rolls back; host reads between steps only inspect.
An unfinished request, read-out, or write sets `save_write_pending()`, which blocks host persistence.
## Bus and commands
Only `0x0e000000` through `0x0e00ffff` are mapped. Accesses are byte-only.

View file

@ -6,6 +6,7 @@ Hardware references used for the core:
- [GBATEK ARM instruction cycle times](https://problemkaputt.de/gbatek-arm-cpu-instruction-cycle-times.htm).
- [GBATEK GBA system control and WAITCNT](https://problemkaputt.de/gbatek-gba-system-control.htm), including HALTCNT power-mode selection.
- [GBATEK GBA cart backup EEPROM](https://problemkaputt.de/gbatek-gba-cart-backup-eeprom.htm), for the 0x0d mapping by ROM size, request and read-out bitstreams, bit-0 bus, DMA3-only transfers, 10 decoded address bits, and the ~108,368-cycle write.
- [GBATEK GBA unpredictable things](https://problemkaputt.de/gbatek-gba-unpredictable-things.htm): with no cartridge, the ROM area reads as `Address/2 AND FFFFh`, because the Game Pak shares its data lines with the low address lines. The emulator applies the same undriven-bus pattern beyond the ROM chip.
- [mGBA `src/gba/memory.c`](https://github.com/mgba-emu/mgba/blob/master/src/gba/memory.c): out-of-bounds ROM loads return `(address >> 1) & 0xFFFF` per halfword. mGBA applies it from the image size; this emulator keeps bytes up to the power-of-two chip boundary diagnostic.
- [GBATEK BIOS reset functions](https://problemkaputt.de/gbatek-bios-reset-functions.htm), for SoftReset state and restart selection, plus RegisterRamReset flags, RAM bounds, and forced blank.

View file

@ -83,6 +83,7 @@ Update this file when a feature lands or a limit is removed.
- Explicit Macronix Flash64/Flash128 identification, bank selection, delayed byte programming, sector/chip erase, and nominal DQ7 polling.
- Transactional Flash completion and modification tracking, with exact-size core image loading and inspection.
- Explicit 32 KiB byte-wide SRAM (`--save-type sram`), mirrored through the save window and readable from ROM code.
- Explicit 512-byte and 8 KiB serial EEPROM (`--save-type eeprom512|eeprom8k`) over DMA3 bitstreams, with nominal write busy time.
- Optional desktop `--save-file` loading and clean-exit persistence, with exclusive locks, atomic installation, and unique previous-image backups.
- Original ROM save/restart/load checks across processes, including native-window persistence on Darwin arm64.
- Whole-machine [save states](hardware/save-states.md): `Machine::save_state`/`load_state` with image and cartridge identity checks, a checksum, and verify-before-mutate loading; ROM windows bind F5 (save) and F9 (load) to a `--state-file` slot.
@ -156,7 +157,7 @@ Individual fetch timing, hardware-accurate work cutoffs, video-bus contention, a
ROM windows offer opt-in Darwin audio output with `--audio` and explicit save persistence with `--save-file`.
The [Flash subset](hardware/saves.md) supports programming and erase with nominal delays and bounded DQ7 polling.
Flash timing, STOP clocking, and cancellation are not hardware-verified. Wider accesses and unverified busy addresses remain diagnostic.
EEPROM, other Flash manufacturers, SRAM halfword/word access, autosave, crash recovery, and game-level save transactions remain unsupported.
Other Flash manufacturers, SRAM halfword/word access, CPU-driven EEPROM bitstreams, autosave, crash recovery, and game-level save transactions remain unsupported.
Persistence is tested on Darwin arm64 with original programs, not yet with external-game saves.
The optional [cartridge GPIO/RTC subset](hardware/cartridge-gpio.md) supports calendar/control transfers and explicit clock updates, selected with `--rtc` in ROM modes.
RTC persistence, interrupts, invalid-date correction, physical subsecond timing, GPIO aliases, and byte writes remain unsupported or unverified.
@ -233,7 +234,7 @@ The unmodeled internal memory control register at `0x04xx0800` and all unused I/
CPU and DMA reads beyond the power-of-two ROM chip return the Game Pak halfword-address pattern; reads inside a trimmed image's chip, host reads, and instruction fetches past the image return errors.
Direct memory-bus halfword and word accesses require alignment.
CPU load/store instructions apply ARM7TDMI alignment and rotation rules before accessing the bus.
CPU and DMA writes to the `0x08`–`0x0c` ROM windows are ignored; the `0x0d` window stays diagnostic until EEPROM exists. Explicitly selected cartridge GPIO and supported Flash command writes are separate from ROM storage.
CPU and DMA writes to the `0x08`–`0x0c` ROM windows are ignored; the `0x0d` window stays diagnostic unless EEPROM is selected. Explicitly selected cartridge GPIO and supported Flash command writes are separate from ROM storage.
## Next steps