Z80/Недокументированные команды
Большинство операционных кодов процессора Z80 имеют в длину один байт (если не считать операндов). Но есть команды, в которых перед операционным кодом ставится префикс CB, DD, ED или FD, который изменяет значение следующего за ним операционного кода.
Всего существует 248 операционных кодов с префиксом СВ. Целый блок таких команд от CB 30 до CB 37 выпал из официального списка. Это инструкции, которые можно обозначить мнемоникой SLL - Shift Left Logical - логический сдвиг операнда влево, при котором нулевой бит всегда становится равным единице. Такие программы как Bounder и Enduro Racer используют эти команды. Мой монитор, входящий в SamRam, дисассемблирует эту операцию и дает ей мнемонику SLL. Эта группа команд используется очень широко.
Операционные коды DD и FD предшествуют инструкциям, использующим регистры IX и IY. Если вы посмотрите на инструкции внимательно, то увидите, как они работают:
2A nn LD HL,(nn) DD 2A nn LD IX,(nn) 7E LD A,(HL) DD 7E d LD A,(IX+d)
Префикс DD просто меняет значение HL в соответствующей инструкции
Если к какому-то байту в памяти проходит косвенная адресация через HL, как показано во втором примере, то добавляется еще байт смещения. В прочих случаях инструкция просто относится к регистру IX, а не к регистру HL. (Кстати, по поводу широкораспространенной путаницы в записи команд, обратите внимание, что JP (HL) - не является косвенной адресацией и путает как программистов, так и авторов ассемблеров и дисассемблеров. Правильно было бы писать JP HL).
Если же код DD поставить перед командой, которая не использует регистр HL, то команда будет исполняться как обычно. Однако, есть исключение для тех команд, которые работают отдельно с регистрами H и L. Если перед ними поставить префикс DD, то они начинают работать с половинками регистровой пары IX, которым нет имен и потому их называют так: IXh - старшая половинка; IXl - младшая половинка. Пример операции:
44 LD B,H DD 44 LD B,IXh
Совершенно аналогично все происходит и с префиксом FD, только теперь все команды относятся не к IX, а к IY. Например:
44 LD B,H FD 44 LD B,IYh
Недокументированные команды этого типа встречаются в очень многих программах. Кстати, иногда применяют длинную последовательность DD или FD для имитации NOP. Такая последовательность не делает ничего, кроме как поочередно перещелкивает флаг "восприятия HL в качестве IX" и тратит на каждом шаге по 4 такта. Но зато попробуйте продисассемблировать такой кусок с помощью MONS'а!
Операции с двойным префиксом DD CB и DD ED ведут себя иначе. Если DD (или FD) стоит перед инструкцией, начинающейся с ED, то DD или FD игнорируются, поскольку операции начинающиеся с ED никогда не работают с регистрами IX и IY. Операция исполняется нормально (без префикса). С инструкциями, начинающимися с префикса CB дело иное, здесь ситуация более интересна и ее мы рассмотрим подробнее.
Прежде всего, обратите внимание на то, что все инструкции, начинающиеся с CB, можно разбить на блоки по 8 инструкций в каждом. Эти 8 инструкций блока относятся к A, B, C, D, E, H, L, (HL). И тогда общее правило поведения инструкции, перед которой стоит двойной префикс DD CB (или FD CB) будет таким:
"Внутри блока из 8-ми инструкций действие каждой команды DD CB ... эквивалентно действию документированной команды CB ..., за исключением того, что параллельно происходит копирование результата в заказанный регистр, кроме тех случаев, когда он (HL)". Пример:
CB CE SET 0,(HL) DD CB nn CE SET 0,(IX+nn) - нет копирования, поскольку имели дело с (HL). CB C0 SET 0,B DD CB nn C0 SET 0,(IX+nn) - результат копируется и в регистр B тоже.
Есть немало недокументированных инструкций, начинающихся с префикса ED, но полезность их непонятна. В диапазонах 00...3F и 80...FF команды с префиксом ED вообще ничего не делают (конечно кроме команд, связанных с обработкой блоков). Но они во-первых занимают по 8 тактов и, во-вторых, увеличивают регистр R на 2. Может быть это и можно где-то использовать.
Больший эффект эти команды имеют в диапазоне 40...7F. Здесь мы приводим их список. Знаком * помечены недокументированные команды.
ED40 IN B,(C) ED60 IN H,(C) ED41 OUT (C),B ED61 OUT (C),H ED42 SBC HL,BC ED62 SBC HL,HL ED43 LD (nn),BC ED63 * LD (nn),HL ED44 NEG ED64 * NEG ED45 RETN ED65 * RET ED46 IM 0 ED66 * IM 0 ED47 LD I,A ED67 RRD ED48 IN C,(C) ED68 IN L,(C) ED49 OUT (C),C ED69 OUT (C),L ED4A ADC HL,BC ED6A ADC HL,HL ED4B LD BC,(nn) ED6B * LD HL,(nn) ED4C * NEG ED6C * NEG ED4D RETI ED6D * RET ED4E * IM 0 ED6E * IM 0 ED4F LD R,A ED6F RLD ED50 IN D,(C) ED70 * IN (C) ED51 OUT (C),D ED71 * OUT (C),0 ED52 SBC HL,DE ED72 SBC HL,SP ED53 LD (nn),DE ED73 LD (nn),SP ED54 * NEG ED74 * NEG ED55 * RET ED75 * RET ED56 IM 1 ED76 * IM 1 ED57 LD A,I ED77 * NOP ED58 IN E,(C) ED78 IN A,(C) ED59 OUT (C),E ED79 OUT (C),A ED5A ADC HL,DE ED7A ADC HL,SP ED5B LD DE,(nn) ED7B LD SP,(nn) ED5C * NEG ED7C * NEG ED5D * RET ED7D * RET ED5E IM 2 ED7E * IM 2 ED5F LD A,R ED7F * NOP
Инструкция ED 70 читает данные из порта, номер которого установлен в регистре С так же, как и другие аналогичные инструкции, но результат... выбрасывает. Единственное, что от ее работы остается - измененные флаги.
Интересна инструкция ED 71 - она выставляет на внешний порт нуль.
Можно догадаться, что в этих двух инструкциях (если учесть регулярность набора системы команд процессора) должны были бы быть команды IN (HL),(C) и OUT (HL),(C). Но процессор Z80 в одной команде дважды к памяти обращаться не может. А внешний порт для то же, что и память. Поэтому такие команды и отсутствуют. Кстати, насчет того, чтобы дважды обращаться в память... - обратите внимание на то, что существуют команды LD A,A; LD B,B, но отсутствует LD (HL),(HL) и вместо нее стоит HALT.
Инструкции ED 4E и ED 6E - эквиваленты команде IM0. Когда в момент прихода прерываний на шине данных стоит FF, Спектрум продолжает нормально работать, но если на шине стоит EF (RST #28), то он сбрасывается - все точно так же, как и в документированном режиме IM0. В режиме IM 1 Z80 исполняет только RST #38 (код FF), независимо от того, что стоит на шине.
Команда RETI - функционально эквивалентна команде RET. Она используется только для того, чтобы дать понять внешнему устройству о том, что исполнение процедуры, обрабатывающей прерывания, закончилось. В то же время, команда RETN - отличается от RET тем, что она устанавливает триггер прерываний IFF1 в текущее значение триггера IFF2. Обычно IFF1 и IFF2 равны между собой (они выравниваются после DI и EI и после приема маскируемого прерывания). Они становятся разными только если происходит немаскируемое прерывание, когда прерывания разрешены. В этом случае IFF1 выключен, а IFF2, сохраняющий предыдущее состояние триггера прерываний, включен и это обозначает, что к моменту прихода немаскируемого прерывания, прерывания были разрешены. Поскольку состояние IFF2 может быть прочитано с помощью LD A,R и LD A,I, то инструкция RETN не используется в ПЗУ Спектрума и бесполезна в обычном программном обеспечении. Другими словами, я и не пытался устанавливать, являются ли неофициальные RET'ы командами RETI или RETN.
Теперь о регистре R. Его нельзя назвать недокументированной особенностью, но подробного описания его я пока тоже нигде не встречал. Фактически это счетчик, который изменяется после исполнения каждой команды. Причем префиксы DD, FD, ED ,CB с его точки зрения рассматриваются как самостоятельные команды, поэтому инструкции с этими префиксами увеличивают регистр R на 2. Интересно, что если команде предшествует двойной префикс DD CB или FDCB, то она тоже увеличивает регистр R на 2. LDIR увеличивает регистр R на 2, умноженное на содержимое BC, то же LDDR и им подобные команды. Последовательность команд LD R,A / LD A,R увеличивает аккумулятор на 2. Старший бит регистра R никогда не меняется (кроме, конечно, команды LD R,A). Это происходит потому, что в старинные времена использовались микросхемы памяти на 16 килобит. Внутри микросхемы биты группировались в матрицу 128 Х 128 и для регенерации памяти достаточно было 7-разрядного цикла. Поэтому фирма ZILOG и решила, что им хватит для регенерации 7-ми битов от регистра R. Если эмуляция регистра R включена, то он ведет себя точно так же, как на Спектруме. Если же она выключена, то регистр R ведет себя как генератор случайных чисел (за исключением старшего байта).
Вы можете легко убедиться в том, насколько важен регистр R для регенерации памяти. ассемблируйте следующую программу:
ORG 32768 DI LD B,0 L1 XOR A LD R,A DEC HL LD A,H OR L JR NZ,L1 DJNZ L1 EI RET
Она будет работать примерно три минуты. После этого загляните в верхние 32К памяти, например в область символов UDG. Вы увидите, что они порушены. Только несколько первых байтов внутри каждого 256-байтного блока будут все еще содержать нули, поскольку они регенерировались во время исполнения цикла. С нижними 16К ОЗУ все будет в порядке, поскольку их регенерацией занимается ULA.
Есть еще один неосвещенный участок в процессоре Z80, который оказывает свое влияние на некоторые программы, например Sabre Wulf, Ghosts'n Goblins и Speedlock. Это недокументированные флаги!
Биты 3 и 5 флагового регистра F не используются. Но они могут содержать информацию и она может быть использована, поскольку существуют такие очевидные команды, как PUSH AF и POP AF. Но, тем не менее, иногда все-таки содержимое этих флагов изменяется. Я нашел следующее эмпирическое правило:
Значения битов 7,5,3 повторяют значения соответствующих битов последнего 8-битного результата инструкции, которая меняла обычные флаги.
Например, после команды ADD A,B эти биты будут идентичны битам в регистре A. Бит 7 регистра F - это знаковый бит и он, естественно, по определению всегда следует этому правилу. исключением является команда CP x (где x - регистр или (HL) или прямой аргумент). В этом случае биты копируются не с результата, а с аргумента.
Для инструкций, которые оперируют с двухбайтными операндами, 8 битов, подпадающие под выведенное правило - это старшие 8 битов из 16-битного результата. Этого и следовало ожидать, поскольку здесь знаковым является старший 15-ый бит.
Ghosts'n Goblins используют недокументированные флаги вследствие ошибки программиста. В программе Sabre Wulf неестественно ведет себя носорог, который иногда бегает малыми кругами в углах, если знаковый бит по команде BIT вычисляется неправильно. Я процитирую:
AD86 DD CB 06 7E BIT 7,(IX+6) AD8A F2 8F AD JP P,#AD8F
Удивительный образец кода! А что же касается "Спидлока", то он выделывает такое количество непонятных и головоломных трюков, что для того, чтобы он работал, в эмуляторе все должно точнехонько соответствовать реальному Спектруму.
Ну и, наконец, в 128-ом ПЗУ тоже есть необычное использование регистра AF - там он использован для временного хранения адреса перехода к подпрограмме.