Memory-Mapped I/O: Гениальный обман процессора

Как заставить процессор считывать нажатия кнопок на джойстике или рисовать пиксели на экране, если он умеет только читать и писать данные в оперативную память? Инженеры придумали гениальный хак, который называется Memory-Mapped I/O (ввод-вывод, отображаемый в память).

Суть в том, что мы берём несколько адресов, которые процессор считает обычной памятью, и тайком подключаем вместо них реальные физические устройства. Процессор выполняет обычную команду «прочитать данные из адреса 254» — и уверен, что обращается к памяти. Но Адресный дешифратор, о котором мы говорили в прошлой статье, перенаправляет запрос в контроллер геймпада. Это настоящая иллюзия матрицы на аппаратном уровне.

Ключ к фокусу — команды загрузки. LDA N загружает непосредственное значение N — само число. А вот ADD N читает данные по адресу N с шины и прибавляет к аккумулятору. Именно ADD — волшебная команда, которая умеет «подслушивать» устройства.

Карта портов компьютера Ершова

Вот какие устройства спрятаны за адресами в игре:

ПортАдресУстройствоНаправление
2500xFALFSR — генератор случайных чиселЧтение
2520xFCMatrixDisplay — координата XЗапись
2530xFDMatrixDisplay — координата YЗапись
2540xFEГеймпад — кнопкиЧтение
2550xFFMatrixDisplay — запись пикселяЗапись
адрес RAM 0x00–0xF9 · программа и данные 0xFA (250) 0xFC (252) 0xFD (253) 0xFE (254) 0xFF (255) LFSR — случайные числа MatrixDisplay — координата X MatrixDisplay — координата Y геймпад — кнопки MatrixDisplay — запись пикселя
Карта адресов компьютера Ершова: обычная память и «притворяющиеся памятью» порты устройств живут в одном пространстве

Как читать кнопки: LDA 0, ADD 254

Чтобы прочитать геймпад, нужны три команды:

LDA 0      ; Acc = 0 — обнуляем аккумулятор
ADD 254    ; Acc = 0 + геймпад = значение кнопок
STA 0      ; сохраняем кнопки в RAM[0]
HLT        ; стоп

Кнопки кодируются битами байта: бит 0 (значение 1) — Вверх, бит 1 (2) — Вниз, бит 2 (4) — Влево, бит 3 (8) — Вправо. Несколько нажатых кнопок складываются в одно число.

Кнопки на числах: 5 = Вверх + Влево

Допустим, игрок держит Вверх и Влево. Кнопки складываются: 1 + 4 = 5. Процессор прочитал из порта 254 байт 5 (0b00000101). Бит 0 равен 1 — нажата Вверх, бит 2 равен 1 — нажата Влево. Программа проверяет биты через AND-маски и двигает персонажа в нужную сторону, а проверки условий мы разбирали в статье про флаги состояния.

По шагам: 1) LDA 0 обнуляет аккумулятор; 2) ADD 254 прибавляет байт с шины — дешифратор видит адрес 254 и включает геймпад; 3) в аккумуляторе оказывается 5; 4) STA 0 сохраняет кнопки в RAM[0]. Именно этого ждёт чекпойнт уровня 2.23.

Пиксели через порты

Дисплей работает по той же схеме: сначала STA 252 — записать координату X, затем STA 253 — координату Y, затем STA 255 с единицей — и пиксель зажжётся. Именно это ты сделаешь на уровне 2.27 «Hello, Ports!», выведя пиксель в точке (4, 12).

Запись по адресу 255 — это не отдельная «волшебная кнопка», а обычная команда STA. Процессор по-прежнему думает, что пишет переменную в память.

Тот же приём на Verilog: уровень 4.46

Memory-Mapped I/O — не только игрушечный трюк. На уровне 4.46 ты реализуешь контроллер ввода-вывода на Verilog: запись по адресу 0xFF зажигает светодиоды (сигнал led_out), а чтение по адресу 0xFE возвращает состояние переключателей (switches_in). Принцип тот же: устройство прячется за адресом, а процессор даже не знает, что разговаривает с железом.

Частые ошибки

1. Написать LDA 254 вместо LDA 0 + ADD 254: LDA загрузит само число 254, а не кнопки из порта.

2. Забыть обнулить аккумулятор перед ADD: к старому значению просто прибавится состояние кнопок.

3. Путать порты дисплея: X — 252, Y — 253, запись пикселя — 255.

Резюме

1. Memory-Mapped I/O — устройства выглядят как обычные адреса памяти.

2. Чтение порта: обнулить аккумулятор (LDA 0), затем ADD <адрес>.

3. Карта портов: геймпад — 254, LFSR — 250, дисплей — 252/253/255.

4. Кнопки — это биты: 1 — Вверх, 2 — Вниз, 4 — Влево, 8 — Вправо.

5. На уровне 4.46 тот же принцип работает на Verilog: светодиоды за адресом 0xFF, переключатели за 0xFE.

На уровне 2.27 тебе предстоит написать программу «Hello, Ports!»: вывести пиксель на матричный дисплей, записывая координаты и цвет через порты 252, 253 и 255. А на уровне 4.46 ты реализуешь контроллер memory-mapped I/O на Verilog — светодиоды и переключатели за адресами 0xFF и 0xFE.

Попробовать в симуляторе →