Skip to content

v1.6

Choose a tag to compare

@acwright acwright released this 16 Sep 14:11
· 43 commits to main since this release

The DS1511Y's 256 bytes of battery-backed NVRAM become a game-save area: 16 slots, addressed by number, that the Kernal reads, writes, validates and enumerates. A game asks "do I have a save, and is it intact?" in one call rather than managing raw bytes.

v1.6 is the last 1.x release. It adds the save slots and one clock-card fix, and nothing else. Later 1.x releases will be bug fixes only; new work goes into BIOS 2.x, which keeps everything below unchanged: the six entries, the slot format, NV_ID at $0390 and NV_PTR = STR_PTR.

The slot format

Slot n occupies NVRAM n*16 to n*16+15, so the slot number is the high nibble of the address:

Offset Contents
+$0 Owner ID. $00 means free; any other value is an identity byte the game chooses
+$1 Checksum
+$2–$F 14 payload bytes

The checksum covers the owner ID and the 14 payload bytes:

ck = $A6
for each byte b:  ck = rotate_left_8(ck) EOR b

The rotate catches transposed bytes, which a plain sum would pass. The nonzero seed stops an all-$FF slot, which is what an erased or unpowered part holds, from checksumming to $FF. Each slot is validated on its own, with no shared directory, so one damaged slot never takes the others with it.

Two header bytes per slot are the price of that. Without them the Kernal could not tell a written slot from a dead battery. 14 bytes still holds a level, a score, lives and a flag word.

Kernal API

Six new jump-table slots, taken from the reserved space without moving any published one. The page stays at 85 slots: 59 published and 26 reserved.

Slot Name Description
$A09F NvStat X=slot → A=NV_EMPTY/NV_VALID/NV_BAD, Y=owner ID
$A0A2 NvRead X=slot, A/Y=destination → copies 14 bytes from a valid slot; A=status, Y=owner ID
$A0A5 NvWrite X=slot, A/Y=source, NV_ID ($0390)=owner ID
$A0A8 NvErase X=slot. Zeroes all 16 bytes, so a deleted save is not left legible
$A0AB NvFind A=owner ID → X=lowest matching slot. A=0 finds the lowest free slot
$A0AE NvFormat Erase all 16 slots

All six follow the same rules:

  • Carry set means the call did nothing. That covers no RTC fitted, a slot of 16 or more, NvWrite with NV_ID = 0, NvFind with no match, and NvRead on a slot that is not valid.
  • A failed NvRead never touches the buffer, and still returns the status and owner ID, so a game can tell "no save yet" from "your save is damaged". NvFind matches damaged slots as well as valid ones for the same reason.
  • X is preserved by NvStat, NvRead, NvWrite and NvErase, so a loop over the slots needs no reload.
  • The caller's decimal and interrupt flags come back unchanged. The checksum's rotate is asl / adc #0, which gives a different answer in decimal mode, so the routines clear D for themselves. A game that keeps its score in BCD can save safely.
  • They check for the RTC themselves, unlike RtcReadNVRAM / RtcWriteNVRAM. A floating bus can produce a pattern that passes a 15-byte checksum by chance, and that would hand a game a save nobody wrote.

The copies use the DS1511Y's burst mode (Control B bit 5, BME), which advances the NVRAM address on every access of the data port. That halves the work of each copy. Interrupts are held off for the few hundred microseconds a copy takes, because an IRQ handler touching NVRAM mid-copy would move the address and corrupt the save. NMI cannot be masked, so an NMI handler must not touch NVRAM. Burst mode is off again on return, so the raw single-byte pair behaves exactly as before.

NvRead, NvWrite and the new equates (NV_SLOTS, NV_EMPTY/NV_VALID/NV_BAD, NV_ID, NV_PTR, RTC_CTRL_B_BME and the rest) are in BIOS.inc. The cost is 314 bytes of Kernal, with 1,230 still free.

Save slots from BASIC

There is no new BASIC statement; the BASIC segment has 41 bytes free, and a statement costs several times that. The existing NVRAM addr,val and NVRAM(addr) already reach every byte, though, and with the format now published a program can implement it directly.

The README ships SAVEMGR.BAS, which lists the 16 slots and has subroutines to read, write and erase one. A save written from BASIC loads in a machine-code game, and the other way round. A test case types the listing straight out of the README and holds it to the ROM in both directions, so the published program cannot drift from the implementation.

Fixed — the clock card went undetected when burst mode was left on

BME is battery-backed and undefined at power-up, like TE beside it. ProbeRTC sets the NVRAM address once, then touches the data port three times: save, write the test pattern, read it back. With BME set, each access moved the address along. The read-back compared against the wrong byte, so HW_RTC was never set and the machine reported no clock card on a board that had one. The restore then wrote the saved byte to a third address.

This was already in the ROM, reachable by any board left with BME set, and the save slots are software that would set it. ProbeRTC now clears BME with a read-modify-write, since Control B also holds TE, before it touches the RAM ports. The case pinning it was run against the v1.5 ROM and watched to fail.

Tests

Seven new cases for the save slots:

  • A round trip at slots 0, 7 and 15, checked byte by byte against the published format over an NVRAM image where every byte differs. A slot that lands one byte off, or a burst that runs past 16, shows up in the other 240.
  • A prepared image with valid, damaged, free, transposed and all-$FF slots. NvStat reports each one correctly, NvRead never copies the bad ones, and NvFind picks the lowest match.
  • NvErase and NvFormat zero everything, D and I come back as they went in, and with no clock card no routine touches the RTC.
  • A save slot survives a reset, and SAVEMGR.BAS agrees with the Kernal.

To check that the cases can fail, the ROM was broken eight ways, including dropping the cld, reading a damaged slot anyway, skipping the RTC check and leaving BME on. A case failed for every one.


The suite is make test167 passed, 0 failed, green in CI against emulator v2.6.0.


Reissued 2026-09-17 — same version, same banner, new bytes

v1.6 now points at 8acb4fc. The ROM's sha256 is 4b4154afac681e26324d3f5a845e41770d977c05db1ef6516c9d2c5e210d8c56; anything pinning the older build should move to it.

The reissue fixes serial flow control against a real R6551, the same four faults as v2.0.1: raising RTS also stops the transmitter, so BASIC's echo could deadlock the machine mid-paste; the interrupt handler could store a character twice; the input ring could lap itself and lose 256 bytes; and every echo reopened the gate a flooded buffer needed closed. Water marks are now $C0/$80, and XModem is exempt throughout.

Nothing else changed: the jump table's slots, the save-slot format, the six NVRAM entries and every version string are as they were. On the bench, pastes to 14 KB arrive byte-perfect with flow control on, with and without the EhBASIC cartridge.