ZX Spectrum System Tour: Memory Mapping | Bumbershoot Software
Today we wrap up the tour of the ZX Spectrum that we started back in May. At this point the only topic left is the memory handlers on the 128K Spectrum. This didn’t see a whole lot of use in the original BASIC tour; the Spectrum’s Z80 CPU can only address 64KB of memory at a time and Sinclair BASICs only make it available to the user as a RAM disk.
Once we dig into what is really on offer, though, it becomes clear that there is more that we can do with it, and in many cases we can even do it from BASIC just as easily as we can from machine code.
We’ve got two things to do today: outline how the 128KB Spectrum’s memory controller is programmed and how it interacts with the rest of the system, and to poke around in the system’s own internals a bit to see how BASIC and the system ROMs themselves use it.
Memory Layout
The 128K Spectrum has, as the name suggests, 128KB of RAM. It also has 32KB of ROM, split into two 16KB halves; one provides the main BASIC and is fundamentally identical to the 48KB system ROM, and the other provides the boot screen, BASIC extensions, and the full-screen editor. The RAM is similarly split into 8 pages of 16KB each, numbered 0-7. Four of these ten memory pages get assigned to the four available slots in the actual CPU address space. The assignment isn’t free, though.
The first slot ($0000–$3FFF ) is always ROM, but it can swap between the "128K" and "48K" ROMs. The second slot ($4000–$7FFF ) holds the system variables and screen memory. As we discussed last week in our review of Cauldwell’s book, there are two pages that can be used for this (5 and 7 in the page numbering, as it happens), allowing for seamless animation via true double-buffered displays. The third slot ($8000–$BFFF ) is permanently hardwired to page 2. The final slot ($C000–$FFFF ) has free access to all of RAM.
This restricts our possible program architectures pretty drastically. BASIC programs load into same page as the screen memory, so we’re likely to always start there during machine code initialization. Page 2 is stable, though, and that will be where we ultimately want to put our core control code. The video pages will toggle back and forth as needed, but any actual computation that relies on multi-page logic is going to need to rely on the stable page 2 and then swap everything else through the final page.
At boot time, the system is configured to be using the system/editor ROM, followed by pages 5, 2, and 7. When running a BASIC program or otherwise being mostly compatible with a 48K Spectrum, the ROM switches over to BASIC ROM and the final page switches to page 0.
Memory Contention
As we saw when we looked at sound, the screen memory interferes with memory access by the CPU, introducing "wait states" that slow down memory access and interfere with precise timing. These wait states are present on both the possible video pages (5 and 7) but also two others. Here the story gets ugly, because the 128K Spectrum was not the last of its kind; it is merely the last mainline Spectrum produced by Sinclair.
Amstrad ultimately got ownership of the line, and there are three further systems generally reckoned as part of the "main line" of Spectrum computers: the +2, +2A, and +3. These offered more robust form factors and built in peripherals, but for the most part we can just treat them as configurations of the 128. However, compatibility is not perfect, and one place where we cannot do this is with wait states.
On the 128 and the +2, the odd numbered pages are the ones with wait states. On the +2A and the +3, it is the last four pages that do. The upshot of this is that any timing-critical code we write had better be in pages 0 or 2—which is to say, the default-mapped $8000–$FFFF range where we’re pretending to be a 48K system.
Later-era Amstrad Spectrums have further capabilities that seem aimed at making them credible platforms to run CP/M on; we can basically ignore those parts here as out of scope. Korth’s Nocash ZX docs give the information on these, as well as the memory on incompatible cousin machines such as the Timex Sinclair 2068.
Programming the Memory Controller
The memory controller is programmed through port $7FFD (32765 decimal). We may access it as easily through BASIC as through machine code, in each case through the OUT instruction. We need merely ensure that we do not annihilate our own program state as part of the swap.
The port is, however, write-only; the system keeps a backup copy of the value at $5B5C (23388 decimal), which we may note is itself in a swappable page. In each case, the byte has five fields in it:
Bits 0-2 ($00–$07) select the RAM page at the top of memory. It may take any value between 0 and 7, and that page of RAM is mapped to $C000–$FFFF .
Bit 3 ($08) selects VRAM. If it’s 0, page 5 is the screen memory; if it’s 1, page 7 is. This gets mapped to $4000–$7FFF , and it is completely legal for the...