No posts yet. Check back soon!
ZX Spectrum System Tour: Memory Mapping
07:30 PM · Aug 15, 2026
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 same memory to be mapped here and in the top slot. - Bit 4 (
$10) controls the ROM at the bottom of memory. If it’s 0, we have the 128K-specific half of the ROM mapped in; if it’s 1, the 48K ROM (which is called into by the 128K mode too) is. - Bit 5 (
$20) locks down the system. Writing a 1 bit here causes all further writes to this port to be ignored. I presume this is used for the 48K Spectrum compatibility mode, since there otherwise isn’t any way to escape from 48K BASIC. - Bits 6 and 7 are ignored. The “special modes” of the +2A and +3 use a completely different I/O port.
The reset state corresponds to a 0 written to the port: 128K ROM and RAM page 5, 2, and 0 in that order.
Messing With It Ourselves
128 BASIC only uses the extended RAM as a RAM disk, or as spare memory to run the full-screen program editor. FUSE’s debugger and memory browser let us see the current configuration as we go along, so let’s wrap up this article by poking arond memory after using these features. We’ll start with a simple program that we can then save out to the RAM disk:
10 PRINT "Hello, world!" SAVE! "Greet"
The SAVE! instruction isn’t part of the program, but we enter it into the same editor and it’s executed immediately, saving our program to the RAM disk. It’s still in the normal BASIC memory too, of course, right after the system variables:

Now we can start a new program: one that will let us mess with the memory port. We’ll need to make sure that our current program stays intact along the way. Fortunately, this is very easy, as we have only three real concerns:
- We need to make sure that the BASIC program we’re running doesn’t get swapped out. Given where it lives, this translates to “leave the screen memory as page 5.”
- We need to make sure that BASIC’s variables don’t get swapped out, including the CPU’s own processor stack. The
CLEARstatement comes to our rescue here; we’ll fit completely within page 5 here so we can demand that we only use that memory with the statementCLEAR 32767. We won’t even need to touch page 2 here. - Finally, we need to make sure that BASIC itself doesn’t get swapped out. The
OUTstatement that accesses the port is part of 48K BASIC, so we need to keep that ROM bank selected when we access it.
In the end, the restrictions on what we write to the port are that the value written should be bitmasked down to a value between 0 and 7, and then the 16 bit should be set alongside it. Here’s my test program:
10 CLEAR 32767: LET PAGE=23388: LET MAPPER=32765 20 CLS 30 LET CURRENT=PEEK PAGE 40 PRINT "Current page: ";CURRENT 50 PRINT "Enter new page (0 quits)" 60 INPUT CURRENT: IF CURRENT=0 THEN GO TO 100 70 LET CURRENT=16+CURRENT-8*INT (CURRENT/8) 80 POKE PAGE,CURRENT: OUT MAPPER,CURRENT 90 GO TO 20 100 POKE PAGE,16: OUT MAPPER, 16
The documentation all suggested that page 7, as well as being the secondary option for screen memory, was also where the full-screen editor kept its data. Let’s take a look!

That looks like screen contents all right, and while we’re at it, there’s some table there with an entry named “Greet”. That looks like it’s the RAM disk’s program directory, but the program itself is not to be found here.
A bit of further exploration find it. It’s in page 1:

It’s fun to see these things, but honestly I do not think a machine code program would benefit much from directly interfacing with the 128’s RAMdisk functionality. It should just be using these pages itself for whatever work it is doing, though I expect that ensuring that there is a sensible world to return to at the end might be a challenge. I expect that the most reliable way to exit a program like that would be to disable interrupts, write a 0 to the memory control port to restore the boot-level memory configuration, and then execute a RST $00 instruction to do a complete system reinitialization.
I do generally prefer programs that return you to the main system when they are done and which may be run repeatedly without reloading; my SpectraLink utility is designed specifically to enable that. However, being able to get that is pretty crucially dependent on having a program that can coexist with the underlying system without trashing it, and I don’t really see any good ways of ensuring that if you’re going to use these mechanisms. That property, as much as I love it, does seem to restrict us to a 48K world.
There and Back Again
And with that, we’ve completed our tour of the three main ZX Spectrum systems. We’ve looked at text and bitmapped graphics, I/O for the keyboard, the joystick, and memory control, sound through both the standard output chips, and we’ve done all of this in both BASIC and machine language. We also developed a high-speed sprite animation system on top of the core bitmap capabilities, and while there were some compromises along the way they are not generally disruptive to the display.
It’s been quite the journey, and I’ve collected all its parts and integrated it into my ZX Spectrum Platform Guide. If you’ve been following along this whole time, thanks for coming along with me here, and I hope you enjoyed the trip too.