Z80/Недокументированные команды

Материал из Emuverse
< Z80
Версия от 17:57, 19 декабря 2007; Panther (обсуждение | вклад) (Новая: {{ДИ|Автор=(с) Г. А. Лунтер (G.A.Lunter), Нидерланды, 1994; (с) Перевод с английского и техническое редактирование...)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)
Данный материал защищён авторскими правами!

Использование материала заявлено как добросовестное, исключительно для образовательных некоммерческих целей.

Автор: (с) Г. А. Лунтер (G.A.Lunter), Нидерланды, 1994; (с) Перевод с английского и техническое редактирование «Инфорком-Пресс», Москва, 1995.

Большинство операционных кодов процессора 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 - там он использован для временного хранения адреса перехода к подпрограмме.