Computing History · Microprocessor Revolution
Two Address Spaces: Port-Mapped and Memory-Mapped I/O Decoding
Why the 8080 family reserved a separate 256-byte I/O space, how the control signals distinguish it from memory, and what each arrangement costs in address decoding and instruction width.
A processor with sixteen address lines can name 65,536 byte locations. Whether a peripheral register lives in that space or outside it is a decision made at the pin level, and it changes the instruction encoding, the decoder, and the failure mode. The 8080 and its descendants keep peripherals outside memory, in a separate space of 256 locations reached only by dedicated instructions. The 6502 keeps everything inside the same 65,536 bytes and distinguishes devices only by which addresses they decode. Both work. The reasons to prefer one are visible in the wiring.
The strobe that says which space is in use
The 8080 signals which space a cycle belongs to through its status outputs, and the system controller that decodes them produces separate strobes for memory read, memory write, I/O read, and I/O write. A read or write to memory asserts one pair of those strobes; a read or write to a peripheral asserts another. There is no third possibility and no address bit that changes meaning. When the processor executes a store to memory, it places the address on A0 through A15, places the byte on the data pins, and the controller asserts the memory write strobe. When it executes an output instruction, it places a port number on A0 through A7 and the same port number duplicated on A8 through A15, places the byte on the data pins, and the controller asserts the I/O write strobe instead.
The duplication is deliberate and documented in Intel’s manual: the port number appears on both halves of the address bus, so a peripheral board can decode the low byte alone and need not care about the high byte’s state. A board that decodes only A0 through A7 for a port will work whether the designer treats the high half as duplicated or as don’t-care. The 8080 does not extend the port space beyond 256, because the instruction that names a port has only eight bits for it. The Z80 adds an indirect form that places a full sixteen-bit value from a register pair on the bus, with the high half coming from one register and the low half from another. That is not the same as duplication, and it is why a Z80 board that decodes the high half sees different values under the two forms; boards that decode only the low byte work with both.
The separate strobes mean the decoder is simpler in one respect and more complex in another. A memory device needs the address lines plus the memory strobe; a peripheral needs the address lines plus the I/O strobe. Because the two strobes are mutually exclusive, a designer can wire a single decoder that produces a chip select for a memory region and a second chip select for a port region without worrying that both go active. On the 6502 there is no I/O strobe at all. A peripheral asserts its select when the address falls in its assigned range and the read/write line has the right polarity, which means the address decoder must be complete enough to exclude every memory device from that range.
What the instruction set charges for each choice
On the 8080, an input or output instruction is two bytes: one opcode that names the operation and one byte that names the port. That is the minimum possible for a dedicated space of 256, and it is one byte shorter than the equivalent memory access, which needs three bytes to carry a sixteen-bit address. A driver that polls a status register and writes a data register can be written as repeated two-byte instructions with the port numbers embedded. The program is compact and the port numbers are visible in a disassembly as immediate bytes.
The cost appears when a program wants to treat a peripheral as data. A block move between memory and a port cannot use the processor’s string instructions, because those operate only on memory. Each transfer is an explicit load, store pair with a fixed port number, which means the loop body cannot be parameterised by a port held in a register. Code that services a variable device must either dispatch to separate loops or rewrite its own instructions. The Z80’s indirect form removes the restriction by letting the port number come from a register pair, at the cost of a longer instruction and an extra machine cycle.
On the 6502 the situation inverts. A peripheral register is an ordinary memory address, so it can be reached through any addressing mode the instruction set offers: absolute, absolute indexed by X or Y, indirect through a pointer in zero page. A routine that copies a block of bytes into a device can be a single indexed store inside a loop, with the device address in a register. The cost is that the device occupies memory that programs might want, and that every access is a full memory cycle indistinguishable at the bus from a RAM access.
Decoding a device into an address range
The practical difference shows up in the decoder logic. Consider a machine that must place a UART at two consecutive addresses and a timer at two more, without overlapping RAM or ROM.
For the 8080 with port-mapped peripherals, the decoder takes A0 through A7 plus the I/O strobe and produces four chip selects. The high address byte is ignored. The logic is small and the port map is a flat list of 256 entries with no interaction with the memory map at all. Adding a device does not require re-planning memory.
For the 6502 with memory-mapped peripherals, the decoder must look at enough address lines to distinguish the device range from every other range, and it must account for the fact that unused address lines float to whatever value the program left them at. A device decoded on only part of the address will appear at multiple addresses, a phenomenon called mirroring. A device that responds when A4 through A11 match one particular value, and ignores A0 through A3 and A12 through A15, occupies sixteen bytes in each of the sixteen 4 KB blocks, so a stray write anywhere in a matching sixteen-byte slot of any block lands on the device. That is sometimes deliberate, because it lets a short program reach the device with an eight-bit address, and sometimes a bug, because the extra copies shadow memory the program meant to use.
; 6502: a write to a mirrored device range. The assembler emits absolute
; addressing, three bytes, but the device only decodes A4..A11.
LDA #$01
STA $C010 ; sets a bit on a device mapped at $C0x0
STA $D010 ; same device: A4..A11 match, higher bits ignored
The 8080 avoids mirroring inside its port space only because the space is small enough to decode fully. A 256-entry space needs eight address bits and one strobe, and every combination is meaningful. Memory-mapped designs on the same processor, used by many systems for video RAM and ROM cartridges, face the same mirroring question as the 6502.
Failures that are specific to each arrangement
Port-mapped I/O fails when code assumes the port space is larger than it is. An eight-bit port number cannot name a device at port 300, and a driver written for a machine with a sixteen-bit port space must be rewritten rather than recompiled. It also fails when the two strobes are not actually exclusive, which happens when a peripheral board decodes the memory strobe by mistake and responds to ordinary memory traffic.
Memory-mapped I/O fails in a different set of ways. A read from a device register may have side effects, such as clearing a status flag or advancing a FIFO, and the processor has no way to distinguish that read from a read of ordinary memory. Compilers and hand-written code that read a location twice for convenience will consume two events. The 6502’s read-modify-write instructions make this concrete: an instruction that increments a memory location performs a read, then a write of the old value, then a write of the new value, so a device register at that address sees three bus transactions rather than one. Hardware designers learned to keep such registers away from addresses that programs might treat as variables.
Volatile qualification exists in later languages precisely to describe this: a location whose value can change without the program writing it, and whose accesses must not be merged or reordered. On an eight-bit processor with no memory-ordering hardware, the annotation is a promise to the compiler rather than a bus protocol, and the bus delivers whatever the instruction stream asks for. A device that occupies memory also has to be excluded from the memory the operating system allocates, which is one reason display memory sits at a fixed high address rather than wherever the allocator finds room.
The third failure is shared. Neither arrangement gives a device a way to say it is not ready. A port write to a printer that is still busy is a byte lost, unless the device has a status bit the program polls or an interrupt line it can assert. That handshake is a separate design problem from the addressing question, and it is where the next layer of the machine’s structure begins: the firmware’s interrupt vectors exist in part to hide which of these two arrangements a particular adapter uses.
References
- MCS-80/85 Family User Manual — Intel Corporation
- Zilog Z80 CPU User Manual (UM008) — Zilog
- MCS6500 Family Hardware Manual — MOS Technology, 1976