Everything a 48K Spectrum does with the outside world goes through one I/O port. The keyboard, the border, the speaker and both tape sockets share port $FE, and the ULA that answers it looks at a single address line to decide whether a request is meant for it. That economy saved gates on the board and handed the cost to everyone else: every peripheral maker had to pick an address the ULA would leave alone, and every programmer had to know which bits of a port address the hardware was really reading. The 128K machines added ports for paging and sound, and Amstrad’s redesign for the +2A and +3 changed how some of them are decoded. This page gives the map, model by model.
How the Z80 puts a port address on the bus
The Z80 has a separate 64K I/O address space. IN A,(n) and OUT (n),A put the immediate byte on A7-A0 and the accumulator on A15-A8; IN A,(C) and OUT (C),A put the whole of BC on the bus. The Spectrum uses that high byte. Reading the keyboard, it chooses which keys to read; on the 128K machines it is part of the paging port’s address.
Sinclair’s manual for the 48K machine sets out the house rule for the low byte: “Bits A0, A1, A2, A3 and A4 are the important ones. They are normally 1, but if any one of them is 0 this tells the computer to do something specific.” The ULA takes A0. The ZX Printer takes A2, which is why its port is 251. Ports 247 and 239 (A3 and A4 low) are kept for the Interface 1 devices of the manual’s chapter 22, and the +3 manual still lists 254, 247 and 239 as reserved.
Port $FE: the ULA decodes one address line
Chris Smith, who reconstructed the ULA from its silicon, puts the rule plainly: the ULA “takes the partial decoding of this address to the extreme by considering only address line A0.” Any even port number selects it. The scheme came from the ZX80, built from standard logic chips, where “every additional gate increased the risk of requiring an additional integrated circuit”. The cost is a rule for software: address the ULA as $FE, with A7-A1 all high, so that a peripheral decoding one of those lines does not answer at the same time.
All the Spectrum models keep this port and its bit layout. A write latches border, tape output and speaker; a read returns keyboard and tape input. The two directions are separate circuits, so reading the port does not tell you what was last written to it.
Written
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Purpose | - | - | - | Speaker | MIC | Border | Border | Border |
The 48K manual gives the same map in one sentence: port 254 “drives the loudspeaker (D4) and the MIC socket (D3), and also sets the border colour (D2, D1 and D0)”. Bits 5-7 are ignored.
The border is not a memory location. Smith explains that fetching a byte from memory every time the display needed the colour would cost too much, so the ULA keeps it in “a latched three bit I/O output port that directly feeds the display controller”. A write takes effect straight away, which is what makes border stripes and split-colour borders possible: change the colour while the picture is being drawn and the next stretch of border comes out in the new colour.
ld a,2 ; red border, MIC and speaker bits clear
out ($FE),aEvery write sets all five bits at once, so a program that changes the speaker must also write the current border colour, and the other way round. The ROM keeps the border colour in the system variable BORDCR (23624, $5C48), which the BORDER command updates. It stores the colour “multiplied by 8”, in bits 3-5, because the same byte serves as the attribute for the lower part of the screen. The ROM’s BEEPER routine shows how to turn it back into a port value: LD A,(BORDCR), AND +38, three RRCAs to bring the colour down to bits 0-2, then OR +08 to keep the MIC output off. A game that has taken over the machine keeps its own copy of the byte instead. Beeper music shows what programmers built on bit 4.
Read
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Purpose | - | EAR | - | Key | Key | Key | Key | Key |
| Value | 1 | tape input | 1 | 0 = pressed | 0 = pressed | 0 = pressed | 0 = pressed | 0 = pressed |
The manual: “bits D0 to D4 stand for the five keys in the given half row - D0 for the outside key, D4 for the one nearest the middle. The bit is 0 if the key is pressed, 1 if it is not. D6 is the value at the EAR socket.” The ULA does not drive bits 5 and 7, and on the 48K machine they read as 1. Smith notes that D5 would have been “the logical choice” for the tape input, but the silicon cell behind that pin was mostly taken up by the analogue cassette circuit, so the job went to D6.
Mask with AND $1F before testing keys. Do not rely on bit 6 when no tape is playing. On the 48K it also picks up what the program last wrote to the MIC and speaker bits, and Issue 2 and Issue 3 boards respond differently, which is how a well-known test tells them apart. The +2 and +3 manuals simply call it “effectively random if no tape data is present”.
Which half-row
The 40 keys are wired as eight half-rows of five. The high byte of the port address picks a half-row by holding one of A8-A15 low. The manual gives the decimal form, 254+256*(255-2^n) for n = 0 to 7, and lists the rows from “IN 65278 reads the half row CAPS SHIFT to V” to “IN 32766 reads the half row SPACE to B”.
| High byte | Line held low | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|
| $FE | A8 | V | C | X | Z | CAPS SHIFT |
| $FD | A9 | G | F | D | S | A |
| $FB | A10 | T | R | E | W | Q |
| $F7 | A11 | 5 | 4 | 3 | 2 | 1 |
| $EF | A12 | 6 | 7 | 8 | 9 | 0 |
| $DF | A13 | Y | U | I | O | P |
| $BF | A14 | H | J | K | L | ENTER |
| $7F | A15 | B | N | M | SYMBOL SHIFT | SPACE |
Bit 0 is the key at the outer end of the row (CAPS SHIFT, A, Q and 1 on the left; SPACE, ENTER, P and 0 on the right) and bit 4 the key nearest the middle. The manual’s own demonstration prints the raw byte for each row in turn:
10 FOR n=0 TO 7: REM half-row number
20 LET a=254+256*(255-2^n)
30 PRINT AT 0,0; IN a: GO TO 30In machine code, put the half-row in B and $FE in C, then IN A,(C). The ROM’s KEY-SCAN routine at $028E does exactly this. It starts with LD BC,+FEFE, reads a row, inverts and masks it (CPL, AND +1F) so that a pressed key becomes a set bit, and rotates B with RLC B to move the low bit through all eight rows.
; Is P pressed? Row $DF, bit 0.
ld bc,$DFFE
in a,(c)
bit 0,a
jr z,p_pressed ; bit clear = key downHold more than one line low and the rows are combined: Smith says multiple rows “are read by logically ANDing high bytes together”. XOR A followed by IN A,($FE) selects all eight rows, so after CPL and AND $1F a non-zero result means some key is down. The ROM’s KEY-SCAN gives up when more than two keys are pressed, or two that are not a shift and a key, but a game reading the matrix itself can track as many as it likes, within the limit of the wiring.
Phantom keys
The matrix has diodes only where it meets the address lines, none at the keys, so three keys pressed in the right places read as four. Smith’s example is Q, P and O. Q joins A10 to the bit 0 input; P joins A13 to the same input, so when A10 is held low to read the Q-T row, A13 is pulled low with it; and O, joining A13 to the bit 1 input, then pulls bit 1 low too. “The read of keyboard half-row Q to T would incorrectly indicate that Q and W were being pressed.” Q, O and P are three of the four keys in the common Q-A-O-P layout.
The rule: if three pressed keys sit on three corners of a rectangle in the table above, the fourth corner reads as pressed too. Check any control scheme that expects three keys at once for rectangles.
The speaker: bit 4
Sound on a 48K Spectrum is bit 4 of port $FE, switched by the processor. There is no tone generator: pitch is the time between switches and the volume is fixed. The ROM’s BEEPER routine at $03B5 disables interrupts, counts T-states in a timing loop and flips the bit with XOR +10 on each half-cycle. Beeper music covers what games did with the same idea.
Joysticks
Kempston: port $1F
Kempston Micro Electronics’ interface put its joystick on port 31 ($1F). The address is odd, so the ULA ignores it. Kempston’s own instructions explain it with a BASIC loop, 10 PRINT IN 31;:GO TO 10, which prints 0 with the stick centred, and a chart of the values: right 1, left 2, down 4, up 8, with “either fire buttons” adding 16.
| Bit | Function |
|---|---|
| 0 | Right |
| 1 | Left |
| 2 | Down |
| 3 | Up |
| 4 | Fire |
Unlike the keyboard, a Kempston bit is 1 when active. The interface decodes only part of the address too: the comp.sys.sinclair FAQ gives its pattern as A5, A6 and A7 low, which $1F satisfies. Many other interfaces copied the protocol; the RAM Turbo’s manual tells owners they can “read the Kempston port directly from Basic using IN 31”.
in a,($1F)
bit 4,a
jr nz,fireSinclair-style: through the keyboard
The joystick sockets built into the grey +2, the +2A and the +3 take a different route. They are wired into the keyboard matrix, like Sinclair’s Interface 2, and read as the number keys. The +2 and +3 manuals give the layout in the same words: “For JOYSTICK 1, bit 0 is fire, bit 1 is up, bit 2 is down, bit 3 is right and bit 4 is left. For JOYSTICK 2, bit 0 is left, bit 1 is right, bit 2 is down, bit 3 is up and bit 4 is fire.” Joystick 1 sits on the 6-0 row ($EFFE) and joystick 2 on the 1-5 row ($F7FE), so a game that reads those rows of the keyboard supports them without knowing they are there.
128K memory paging: port $7FFD
The Spectrum 128, the grey +2, the +2A and the +3 have eight 16K RAM banks. The 128 and grey +2 have two 16K ROMs; the +2A and +3 have four. All four machines switch memory through port $7FFD (32765). The +3 manual gives the bit field:
| Bit | Function |
|---|---|
| 0-2 | RAM bank paged in at $C000-$FFFF (0-7) |
| 3 | Screen select: 0 = screen 0 in bank 5 (at $4000, the one BASIC uses); 1 = screen 1 in bank 7, which “can only be used by machine code programs” |
| 4 | ROM select. On the 128 and grey +2: 0 = 128K editor, 1 = 48K BASIC. On the +2A and +3, the low bit of the ROM number (see $1FFD) |
| 5 | Disable paging: “once this bit has been set, no further paging operations will work” until power-off or reset |
| 6-7 | Unused |
In normal paging, bank 5 sits at $4000 and bank 2 at $8000, and only the top 16K moves. Both screens live in banks that can also be paged in at $C000, which is how a program draws into the hidden screen: page bank 7 in at $C000, draw, then flip bit 3 to show it. Bit 5 is the lock the +3 manual suggests for a 48K game that will not run: SPECTRUM, then OUT 32765,48, leaves the machine as a 48K Spectrum with paging dead until reset, “however, the sound chip can still be driven by OUT”.
Decoding: where the models differ
Like the ULA, the paging port is partially decoded, and Amstrad changed the decoding when it redesigned the machine:
| Models | Responds when | Also answers at |
|---|---|---|
| 128, grey +2 | A15 and A1 low | any such address, including $1FFD, $3FFD, $5FFD |
| +2A, +3 | A15 low, A14 high, A1 low | $5FFD, but not $1FFD or $3FFD |
Software written for the +3 must not assume its other ports are harmless on the older machines: on a 128 or grey +2, a write intended for $1FFD lands on the paging register.
The port is write-only. The +3 manual spells out the consequence: “you cannot determine the current state of the paging by an IN instruction. This is why the BANKM system variable is always kept up to date with the last value output to this port”. Reading $7FFD returns whatever is floating on the data bus. Keep a copy of every value you write, and update the copy and the port together. The +2 manual adds the usual traps: check “that your stack is not being paged out during interrupts, and that your interrupt routine is always where you expect it to be! (it is advisable to disable interrupts during paging operations)”.
Contended banks
Some banks share time with the video circuitry, so code running in them is slowed down. Which banks they are depends on the model:
| Models | Contended | Uncontended |
|---|---|---|
| 128, grey +2 | 1, 3, 5, 7 | 0, 2, 4, 6 |
| +2A, +3 | 4, 5, 6, 7 | 0, 1, 2, 3 |
The +3 manual gives the cost on its own machine: executing NOPs in contended RAM “will give an effective clock frequency of 2.66MHz as opposed to the normal 3.53MHz”. Amstrad’s manual for the grey +2 also says banks 4 to 7, in the same words; but on the 128 and grey +2 it is the odd-numbered banks that are contended. Time-critical code and sample data belong in an uncontended bank for the model in hand. Bank switching covers the technique across machines.
+2A/+3 paging: port $1FFD
The +2A and +3 add a second paging port, $1FFD (8189), answered when A15-A12 read 0001 and A1 is low. It is write-only too, and +3 BASIC keeps a copy in the system variable BANK678. From the manual:
| Bit | Function |
|---|---|
| 0 | 0 = normal paging; 1 = one of four all-RAM layouts, chosen by bits 1-2 |
| 1 | No effect when bit 0 is 0; selects the all-RAM layout when bit 0 is 1 |
| 2 | With bit 0 clear, the high bit of the ROM number. Bit 4 of $7FFD is the low bit |
| 3 | Disk motor, 1 = on |
| 4 | Printer STROBE, active low |
The two ROM bits pick one of four ROMs: 0, the 128K editor; 1, the syntax checker; 2, +3DOS; 3, 48 BASIC.
With bit 0 set, the whole 64K becomes RAM in one of four arrangements. They are “not used by +3 BASIC but are provided for authors of operating systems/games”:
| Bits 2-1 | $0000 |
$4000 |
$8000 |
$C000 |
|---|---|---|---|---|
| 00 | 0 | 1 | 2 | 3 |
| 01 | 4 | 5 | 6 | 7 |
| 10 | 4 | 5 | 6 | 3 |
| 11 | 4 | 7 | 6 | 3 |
The last is the one +3DOS uses when it loads a disk’s bootstrap: “the 4, 7, 6, 3 RAM page environment”.
Disk and printer ports
The +3’s floppy disk controller, a µPD765A, has two ports: $2FFD reads its main status register and $3FFD is its data register. Only the +3 has a disk controller behind them. The manual is blunt about the second: “Random OUTputting to this port will probably confuse the poor disk controller chip”.
The Centronics printer port is $0FFD (4093). Read, one bit shows the printer’s BUSY signal; written, it is the data register. To print a character, wait until BUSY reads 0, write the character to $0FFD, then take the STROBE bit of $1FFD low and back high. The +2A has the same printer socket as the +3; dealers sold a single “+3/+2A Printer Lead”.
AY-3-8912 sound: ports $FFFD and $BFFD
The 128K models’ AY-3-8912 has its registers behind two ports, the same on all four machines. Write the register number to $FFFD (65533), then write the value to $BFFD (49149); reading $FFFD returns the selected register. The FAQ gives the decoding as A15 high, A1 low, and A14 high for $FFFD or low for $BFFD.
Register 7 needs care. Its low six bits switch tone and noise on each channel (0 = on), but its top two bits set the direction of the chip’s I/O port, and on these machines that port drives the RS232/MIDI and keypad or AUX sockets. As the manual warns, the sound ports “also control RS232/MIDI and AUX interfaces”. Read the register first and keep the top two bits as they were:
ld bc,$FFFD
ld a,7 ; select the mixer register
out (c),a
in a,(c) ; read its current value
and %11000000 ; keep the I/O port direction bits
or %00111110 ; tone on channel A only
ld b,$BF ; BC = $BFFD
out (c),aSources
- Steven Vickers, Sinclair ZX Spectrum BASIC Programming, edited by Robin Bradbeer (Sinclair Research, 1982; second edition 1983), chapter 23, “IN and OUT”: the A0-A4 rule; the half-rows and the
254+256*(255-2^n)formula; the read and write formats of port 254; the printer on 251; ports 254, 247 and 239 for the chapter 22 devices; the demonstration program. - Chris Smith, The ZX Spectrum ULA: How to Design a Microcomputer (ZX Design and Media, 2010), chapter 19, pp. 210-216: A0-only decoding and its ZX80 origin; the border latch; MIC on D3, speaker on D4, EAR on D6 and why not D5; half-rows combined by ANDing high bytes; diodes at the address lines. Chapter 23, pp. 245-246: the Q, P and O phantom key.
- Ian Logan and Frank O’Hara, The Complete Spectrum ROM Disassembly (Melbourne House, 1983): KEY-SCAN at
$028E; BEEPER at$03B5(itsDI, the BORDCR conversion withOR +08, andXOR +10); the BORDER command at$2294storing the colour times eight in BORDCR. - Amstrad, Sinclair ZX Spectrum +2 manual (1986): part 23, p. 139 (port 254, the joystick bits, port 32765 write-only, the sound ports); part 24, p. 147 (the paging bits and its contended-RAM wording); part 25 (BORDCR at 23624, “Border colour multiplied by 8”); part 26, p. 154 (interrupts and paging); part 29, pp. 166-167.
- Amstrad, ZX Spectrum +3 manual (1987), chapter 8: part 23, pp. 180-181 (port 254 and its D6, the joystick bits, the reserved ports,
$7FFDwrite-only and BANKM, the sound ports,$0FFD,$1FFDand BANK678,$2FFD,$3FFD); part 24, pp. 189-191 (the$7FFDbit field, the lock andOUT 32765,48, contended pages 4-7 and the 2.66 MHz figure, ROM switching, the all-RAM modes and the 4, 7, 6, 3 boot layout); parts 25-27 (ROM 3 “the 48 BASIC ROM”, ROM 2 “the DOS ROM”, and ROM 1 reporting errors to ROM 3). - Kempston Micro Electronics, Kempston Joystick Interface instructions (1984): port 31, the test loop, the direction values and fire adding 16.
- RAM Electronics, RAM Turbo Interface manual: “You can read the Kempston port directly from Basic using IN 31”, and the Sinclair-style joystick rows.
- Alessandro Grussu, Spectrumpedia, volume 1 (English edition, CC BY-NC-ND 4.0), p. 124 (banks 1, 3, 5 and 7 contended on the 128) and p. 149 (the +3’s four all-RAM layouts).
- Sinclair User 130, December 1992: mail-order advertisement listing a “+3/+2A Printer Lead” at £11.95.
- comp.sys.sinclair FAQ, hosted by World of Spectrum (web, secondary): “Hardware ports” (partial decoding patterns for the ULA, Kempston, AY, paging and +3 disk ports), https://worldofspectrum.org/faq/reference/ports.htm; “48K reference” (bits 5 and 7 read as one; the Issue 2/Issue 3 bit 6 difference), https://worldofspectrum.org/faq/reference/48kreference.htm; “128K reference” (
$7FFDand$1FFDdecoding by model, contended banks by model, reading$7FFD), https://worldofspectrum.org/faq/reference/128kreference.htm
See also
- ZX Spectrum
- Z80 - the CPU whose I/O instructions this page relies on
- ULA - the chip behind port $FE
- AY-3-8912 - the 128K sound chip
- Beeper music - what to do with bit 4
- Bank switching - the 128K paging technique in context
- ZX Spectrum ROM disassembly - the ROM’s own keyboard, border and beeper routines