Memory-Mapped I/O: Гениальный обман процессора
Как заставить процессор считывать нажатия кнопок на джойстике или рисовать пиксели на экране, если он умеет только читать и писать данные в оперативную память? Инженеры придумали гениальный хак, который называется Memory-Mapped I/O (ввод-вывод, отображаемый в память).
Суть в том, что мы берём несколько адресов, которые процессор считает обычной памятью, и тайком подключаем вместо них реальные физические устройства. Процессор выполняет обычную команду «прочитать данные из адреса 254» — и уверен, что обращается к памяти. Но Адресный дешифратор, о котором мы говорили в прошлой статье, перенаправляет запрос в контроллер геймпада. Это настоящая иллюзия матрицы на аппаратном уровне.
Ключ к фокусу — команды загрузки. LDA N загружает непосредственное значение N — само число. А вот ADD N читает данные по адресу N с шины и прибавляет к аккумулятору. Именно ADD — волшебная команда, которая умеет «подслушивать» устройства.
Карта портов компьютера Ершова
Вот какие устройства спрятаны за адресами в игре:
| Порт | Адрес | Устройство | Направление |
|---|---|---|---|
| 250 | 0xFA | LFSR — генератор случайных чисел | Чтение |
| 252 | 0xFC | MatrixDisplay — координата X | Запись |
| 253 | 0xFD | MatrixDisplay — координата Y | Запись |
| 254 | 0xFE | Геймпад — кнопки | Чтение |
| 255 | 0xFF | 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.