Выяснилось, что для чтение клавиатуры вместо LDA 8001 там делается IN 81. Вот код оттуда:
Code: Select all
02B4: 01 00 00 LXI B, 0x0000
02B7: 3E FD MVI A, FD ; 11111101 — выбираем строку 1
02B9: D3 80 OUT 80 ; → PPA port A
02BB: DB 81 IN 81 ; ← PPA port B
02BD: FE BF CPI BF ; ArrowRight (бит 6) одиночный?
02BF: C2 C5 02 JNZ 02C5
02C2: 01 01 00 LXI B, 0x0001 ; вправо: dx=+1
02C5: FE EF CPI EF ; ArrowLeft (бит 4) одиночный?
02C7: C2 CD 02 JNZ 02CD
02CA: 01 FF FF LXI B, 0xFFFF ; влево: dx=-1
02CD: CD E4 02 CALL 02E4
02D0: C9 RET
Выходит, что на ванильном железе РК86 идиома OUT 80 / IN 81 из NALET'а работает в точности так же, как стандартная STA 8000 / LDA 8001.
Почему:
Что делает 8080 при IN port / OUT port
У Intel 8080 одна 16-битная адресная шина и одна шина данных, никакого отдельного «I/O space» нет. На цикле ввода-вывода процессор:
1. Кладёт 8-битный номер порта на обе половины адресной шины — на A0..A7 и на A8..A15. Это задокументированное поведение 8080, оно описано в любом мануале по процессору.
2. В момент SYNC выставляет на шине данных статусное слово, помечающее цикл как «I/O read» либо «I/O write» — снаружи это превращается в сигнал IO/M (или производные INP* / OUT*).
То есть OUT 80h на циклах шины кладёт на адресную шину 8080h, IN 81h — 8181h. Полностью обычный цикл шины, отличается только активным IO/M.
Что с этим делает железо РК86?
Дешифратор chip-select в РК86 — К555ИД7 (74LS138), подключённый только к A13, A14, A15. Он смотрит исключительно на адресную шину — сигнал IO/M в дешифрацию не заведён вовсе. Декодер не отличает обращения к памяти от обращений к портам.
Для OUT 80h:
адресная шина = 8080h → A15..A13 = 100 → chip-select 4 → выбран ППА «D20»
A0..A1 = 00 → порт A ППА шина данных = аккумулятор → ППА защёлкивает значение в PA
Полностью идентично STA 8080h. (А 8080h сам по себе — зеркало 8000h внутри 8К-окна chip-select'а ППА, потому что A2..A12 для чипа don't-care — об этом подробно в разделе «Дешифрация адресов и зеркалирование периферии».)
Для IN 81h:
адресная шина = 8181h → 100 → выбран ППА
A0..A1 = 01 → порт B ППА шина данных = ← столбцы PB → в аккумулятор
Полностью идентично LDA 8181h, которое в свою очередь зеркало LDA 8001h.
Почему обе идиомы сосуществуют в софте под РК86
Монитор и большинство игр используют LDA/STA по именованным адресам ППА (8000h/8001h) — так понятнее читать. Но IN/OUT короче (2 байта против 3) и на один такт быстрее:
Code: Select all
┌───────────────────────┬───────────┬──────────────┐
│ Вариант │ Байт │ T-тактов │
├───────────────────────┼───────────┼──────────────┤
│ STA 8000h + LDA 8001h │ 3 + 3 = 6 │ 13 + 13 = 26 │
├───────────────────────┼───────────┼──────────────┤
│ OUT 80h + IN 81h │ 2 + 2 = 4 │ 10 + 10 = 20 │
└───────────────────────┴───────────┴──────────────┘
Где это не сработало бы
Фокус опирается именно на то, что дешифратор chip-select игнорирует IO/M. На машине, где декодер заводит IO/M (или Z80'шный IORQ) в цепь chip-select, OUT 80 и STA 8080 дают разные активации чипов. На таком порте периферию пришлось бы либо перемапить, либо переписать сканирующий код. РК86 IO/M в дешифрацию не заводит — поэтому код NALET'а портабелен между всеми штатными РК86 и большинством байт-совместимых клонов.
Выходит, я чего-то не знал все эти годы?
