Memory-Mapped I/O

How do you make a processor read button presses from a joystick or draw pixels on a screen when all it knows how to do is read and write data in RAM? Engineers came up with a brilliant hack called memory-mapped I/O.

The idea: take a few addresses the processor believes are ordinary memory and secretly attach real physical devices to them. The processor runs an ordinary "read data from address 254" instruction — and is sure it's talking to memory. But the Address Decoder, which we covered in the previous article, routes the request to the gamepad controller instead. It's the Matrix illusion at the hardware level.

The key to the trick is the load instructions. LDA N loads the immediate value N — the number itself. But ADD N reads data from address N on the bus and adds it to the accumulator. ADD is the magic instruction that can "eavesdrop" on devices.

The Ershov Computer's port map

Here's which devices hide behind addresses in the game:

PortAddressDeviceDirection
2500xFALFSR — random-number generatorRead
2520xFCMatrixDisplay — X coordinateWrite
2530xFDMatrixDisplay — Y coordinateWrite
2540xFEGamepad — buttonsRead
2550xFFMatrixDisplay — write pixelWrite
address RAM 0x00–0xF9 · program and data 0xFA (250) 0xFC (252) 0xFD (253) 0xFE (254) 0xFF (255) LFSR — random numbers MatrixDisplay — X coordinate MatrixDisplay — Y coordinate gamepad — buttons MatrixDisplay — pixel write
The Ershov Computer's address map: ordinary memory and "memory-pretending" device ports share one space

How to read buttons: LDA 0, ADD 254

Reading the gamepad takes three instructions:

LDA 0      ; Acc = 0 — clear the accumulator
ADD 254    ; Acc = 0 + gamepad = button state
STA 0      ; store buttons in RAM[0]
HLT        ; stop

Buttons are encoded as bits of a byte: bit 0 (value 1) — Up, bit 1 (2) — Down, bit 2 (4) — Left, bit 3 (8) — Right. Pressed buttons add up into a single number.

Buttons in numbers: 5 = Up + Left

Suppose the player holds Up and Left. The buttons add up: 1 + 4 = 5. The processor reads byte 5 (0b00000101) from port 254. Bit 0 is 1 — Up is pressed, bit 2 is 1 — Left is pressed. The program checks bits with AND masks and moves the character accordingly, and we covered condition checking in the article about status flags.

Step by step: 1) LDA 0 clears the accumulator; 2) ADD 254 adds the byte from the bus — the decoder sees address 254 and enables the gamepad; 3) the accumulator now holds 5; 4) STA 0 stores the buttons in RAM[0]. That's exactly what the level 2.23 checkpoint expects.

Pixels through ports

The display works the same way: first STA 252 writes the X coordinate, then STA 253 writes Y, then STA 255 with a 1 — and the pixel lights up. That's what you'll do in level 2.27 "Hello, Ports!" by drawing a pixel at (4, 12).

Writing to address 255 is not some separate "magic button" — it's an ordinary STA instruction. The processor still thinks it's saving a variable to memory.

The same trick in Verilog: level 4.46

Memory-mapped I/O isn't just a toy trick. In level 4.46 you'll implement an I/O controller in Verilog: writing to address 0xFF lights up LEDs (the led_out signal), and reading address 0xFE returns the switch states (switches_in). Same principle: a device hides behind an address, and the processor never knows it's talking to hardware.

Common mistakes

1. Writing LDA 254 instead of LDA 0 + ADD 254: LDA would load the number 254 itself, not the buttons from the port.

2. Forgetting to clear the accumulator before ADD: the button state just gets added to whatever was there.

3. Mixing up the display ports: X — 252, Y — 253, pixel write — 255.

Summary

1. Memory-mapped I/O makes devices look like ordinary memory addresses.

2. To read a port: clear the accumulator (LDA 0), then ADD <address>.

3. The port map: gamepad — 254, LFSR — 250, display — 252/253/255.

4. Buttons are bits: 1 — Up, 2 — Down, 4 — Left, 8 — Right.

5. In level 4.46 the same principle works in Verilog: LEDs behind address 0xFF, switches behind 0xFE.

In level 2.27 you will write the "Hello, Ports!" program: draw a pixel on the matrix display by writing coordinates and color through ports 252, 253, and 255. And in level 4.46 you'll implement a memory-mapped I/O controller in Verilog — LEDs and switches behind addresses 0xFF and 0xFE.

Try it in the simulator →