On a Commodore 64, POKE 53280,0 turns the border black. Address 53280, $D020, is not memory at all. It is a register inside the VIC-II video chip, wired into the processor’s address space so that a store to that address lands in the chip. That is memory-mapped I/O: every piece of hardware a program needs to talk to has an address, and the program reaches it with the same loads and stores it uses for ordinary memory. For a generation of programmers it was how hardware felt, a list of magic numbers to POKE.
It was not the only design. The Z80 had separate instructions and a separate address space for I/O, and machines built round it, such as the ZX Spectrum, used both kinds. And registers mapped into memory did not behave like memory, which caught out anyone who treated them as if they did.
Fast facts
- Memory-mapped I/O: hardware registers share the address space with RAM and ROM, and ordinary instructions reach them.
- Port-mapped I/O: a separate address space for hardware, reached with special instructions such as the Z80’s
INandOUT. - Processors with no I/O instructions: the 6502 and 6510, the 6809 and the 68000. On these, memory mapping is the only option.
- The catch: an I/O address is not a variable. Some registers cannot be read back, and reading others clears them.
The C64’s page of I/O
Commodore’s Programmer’s Reference Guide draws the 4K at $D000-$DFFF as a single block of I/O:
| Address | Chip | What it does |
|---|---|---|
$D000-$D3FF |
VIC-II | Video: 47 registers, $D000-$D02E |
$D400-$D7FF |
SID | Sound: 29 registers |
$D800-$DBFF |
Colour RAM | One four-bit colour for each of the 1,000 screen positions |
$DC00-$DCFF |
CIA 1 | Keyboard and joysticks, and two timers |
$DD00-$DDFF |
CIA 2 | Serial bus, user port and RS-232; also selects which 16K the VIC-II sees |
$DE00-$DFFF |
Two open I/O slots | For cartridges |
The guide says the two open slots were “tentatively designated for enabling the Z-80 cartridge (CP/M option) and for interfacing to a low-cost high-speed disk system”. The chips do not fill their blocks. The VIC-II has 47 registers in its 1K and the SID 29, so the rest of each block repeats the same registers again.
The same 4K is also where the character generator ROM lives, and what the processor sees there depends on the CHAREN bit of the 6510’s own port at $01. Set “as is normal”, the I/O appears; cleared, “the character ROM appears in the processor address space, and the I/O devices are not accessible”. That port is itself memory-mapped: the 6510 answers at addresses 0 and 1. Memory-mapped I/O on the C64 is therefore one layer of a bank-switched map, not a fixed feature of the address.
Talking to the chips is a store:
lda #$00
sta $d020 ; VIC-II: border colour black
lda #$0f
sta $d418 ; SID: volume to maximum (low four bits)Registers are not memory
Treat a register like a variable and it bites. The SID’s registers, the guide says, “are either WRITE-only or READ-only”: a program that stores a frequency and later reads the same address back does not get its frequency, so a program that needs to know what it wrote has to keep its own copy.
Reading can change things too. The CIA’s interrupt control register is two registers at one address, “a write-only MASK register and a read-only DATA register”, and the data register “is cleared and the IRQ line returns high following a read”. Reading it is how a program acknowledges an interrupt. That is why C64 interrupt code contains loads that seem to do nothing, such as LDA $DC0D with the result thrown away. The raster effects entry has an example.
The NES and the Amiga
The NES’s processor is a 6502 too, so its hardware is memory-mapped. The picture processor (PPU) has eight registers at $2000-$2007, repeated every eight bytes up to $3FFF, and the sound, the controllers and the sprite DMA live at $4000-$4017. The PPU’s own video memory, though, is not in the processor’s address space at all. A program reaches it through a window: it writes an address to $2006, then reads or writes data through $2007, one byte at a time.
The Amiga’s 68000 has no I/O instructions either. Its custom chips answer at $DFF000 onwards, and its two 8520 CIAs sit near $BFD000 and $BFE001, on alternate bytes. The CIA called CIA-A is wired to the low half of the data bus, so it answers only at odd addresses. CIA-B uses the high half and even addresses. Their ports are a mixed bag. CIA-A’s port A at $BFE001 holds the two joystick fire buttons, signals from the disk drive and the LED control, and its port B is the parallel port; CIA-B drives the disk motors and heads and the serial port’s control lines. Commodore’s Hardware Reference Manual treated these hardware addresses as fixed and almost nothing else. Everything in RAM is allocated at run time, it says, and the pointer at location 4 is “the only absolute memory location in the system”.
Ports: the Z80’s other address space
The Z80 has IN and OUT instructions, and an IORQ signal that tells the hardware an access is for I/O rather than memory. Zilog’s manual describes a port number as selecting “the I/O device at one of 256 possible ports”, but both forms of IN and OUT also put a second byte on the upper half of the address bus: the accumulator for IN A,(n), register B for IN r,(C). Designers used it.
The ZX Spectrum uses both styles. Its screen is ordinary RAM from address 16384, which the ULA reads to draw the picture, so drawing is a matter of writing memory. Its keyboard, border colour, speaker and cassette are behind I/O port 254: an OUT to that port, the Spectrum +2 manual explains, “drives the sound (D4) and the save signal to the datacorder (D3), and also sets the border colour (D2, D1 and D0)”. The keyboard shows off the upper address byte. Its 40 keys are wired in eight half-rows, and Adrian Dickens’s Spectrum Hardware Manual explains that each half-row is selected by one of the upper address lines: IN 65278 “uses A8 to read the half row SHIFT to V”, and IN 32766 uses A15 for SPACE to B. Every one of those addresses is port 254 in its low byte.
Other Z80 machines made other choices, and many mixed the two styles. The rule of thumb is the processor’s: on a 6502 or 68000 everything is in memory, and on a Z80 the designer chose for each device.
Sources
- Commodore, Commodore 64 Programmer’s Reference Guide (1982): the VIC-II registers and colour RAM (p. 100); bank selection through CIA 2 (p. 101); the memory map, the I/O block and CHAREN (pp. 260-262); the 6526 CIA’s interrupt control register (Appendix M, pp. 432-433); the SID’s 29 registers (Appendix O, pp. 460-461).
- Amiga Hardware Reference Manual, 3rd edition (Commodore-Amiga, Addison-Wesley): Appendix D, “System Memory Map”, p. 293; Appendix F, CIA address maps, pp. 317-318.
- Zilog, Z80 CPU User Manual (UM008011-0816): the
INandOUTinstructions and the upper address byte. - Adrian Dickens, Spectrum Hardware Manual, 2nd edition (Melbourne House), chapter 7, “The Keyboard”, pp. 32-33.
- Amstrad, Sinclair ZX Spectrum +2 manual, BASIC section, part 23, “IN and OUT”: the port addresses, including port 254 in output.
- NESdev Wiki, “PPU registers” (community reference, web, secondary): the eight registers, their mirroring through
$3FFF,PPUADDRandPPUDATA, andOAMDMAat$4014. https://www.nesdev.org/wiki/PPU_registers