Ассемблер - взгляд издалека Продолжение. Начало в || 20, 21, 24, 25, 28-30, 32 {}Инфарх, 1999 Поговорим о блочных командах Рассмотрим следующий пример. Требуется перенести блок данных длиной в #1B00 байт, расположенный по адресу #C000, в адрес #4000. Программа, выполняющая такое действие, может выглядеть примерно так: LD HL,#C000 LD DE,#4000 LD BC,#1B00 LOOP LD A,(HL) LD (DE),A INC HL INC DE DEC BC LD A,B OR C JR NZ,LOOP RET Этот фрагмент для решения нашей задачи вполне подходит в случае, если перенос должен происходить относительно быстро. Но если скорость для вас - не главное, то его можно и укоротить. А заодно - изучить но- вую команду ассемблера LDI. Оная выполняет действия: LD (DE),(HL) ;такой команды на ;самом деле нет у Z80 INC HL INC DE DEC BC При этом, если содержимое BC равно нулю, то флаг P/V будет установлен в 0. Теперь попробуем переписать пример с учётом новой информации: LD HL,#C000 LD DE,#4000 LD BC,#1B00 LOOP LDI JP PE,LOOP RET Таким образом, программа получилась уже короче. Но разработчики Z80 не остановились на достигнутом и предусмотрели команду LDIR, предназначенную для переноса целого блока. Как и в предыдущем примере, начало блока помещается в регистровую пару HL, длина - в BC, а адрес, куда произойдт перенос, на- ходится в DE. Изобразим схематически её действие: LOOP LDI JP PE,LOOP Теперь наш пример теперь может быть за- писан так: LD HL,#C000 LD DE,#4000 LD BC,#1B00 LDIR RET Область экранной памяти в нашем примере была выбрана не случайно. Загрузите некий экранный файл (#1B00 байт) по адресу #C000 и выполните пример. Результат будет нагля- ден. Кстати сказать, таким образом можно не только создавать экраны, а и чистить области памяти. Попробуйте: LD HL,#4000 LD DE,#4001 LD BC,#1800 LD (HL),L LDIR LD BC,#2FF LD (HL),7 LDIR RET Итак, что оно такое? Начало понятно, это настройка HL на начало экранной памяти. Но в DE помещается значение на единицу больше, а по адресу #4000 помещается "0" (пробел для экрана). Tеперь начинает рабо- тать команда LDIR. Хоть она и предназначе- на для работы с блоками, но перенос проис- ходит всё равно по одному байту. Таким об- разом, каждый раз команда будет переносить в следующий адрес содеримое предыдущего (0), и так до того момента, пока BC не об- ретёт нулевое значение. Вот экран и очищен. Но это касалось только графической области, остались ещё и атрибуты. Вторая половина процедуры ис- пользуется именно для них. Здесь уже не надо задавать значение для HL и DЕ, оно осталось от первого LDIR'а. Надо только задать длину области атрибутов #300-1 (а почему оно так - подумайте сами) и в её начало поместить байт атрибута. Мы ис- пользовали 7, а значит, экран будет иметь чёрную "бумагу" и белые "чернила". А вот теперь... теперь начинается самое стр-р-рашное! Команды LDI и LDIR - далеко не единс- твенные для работы с блоками. Есть ещё LDD и LDDR. Но не волнуйтесь! Разобрться с ними весьма просто. Дело в том, что они произ- водят действия почти полностью аналогичные командам LDI и LDIR, но значения DE и HL не увеличиваются, а уменьшаются. Примерно так: ;команда LDD LD (DE),(HL) DEC HL DEC DE DEC BC ;команда LDDR LOOP LDD JP PE,LOOP Спросите, зачем оно надо? А очень просто! Действительно, если при переносе блоков они друг друга не перекрывают, разницы ни- какой нет. Но представьте себе, что "блок-приёмник" своим началом накрывает окончание "блока-источника". Тогда перенос методом LDIR повредит информацию. Выходом из данной ситуации и служит команда LDDR. Просто надо задать в DE и HL адреса блоков не начальные, а конечные. Длина, ес- тественно, остаётся без изменения. Ну, с одним кошамром мы разобрались, те- перь перейдём к другому. Итак... Работа с портами Так вот - оказывается, для работы с пор- тами тоже есть свои хитрые групповые опе- рации! Для начала рассмотрим мутантов команды IN. Начнём с чего попроще, например, INI: ;пример команды INI IN (HL),(C); (сам придумал) INC HL DEC B Достаточно понятно, вроде. Происходит чтение из порта с заданным адресом в ре- гистр C, полученный байт пересылается в память по адресу, указанному в регистровой паре HL, а регистр B страдает от декремен- та, в результате чего флаг Z или сбросит- ся, или установится. Теперь посмотрим на более извращённый вариант: ;пример команды INIR LOOP INI JR NZ,LOOP Налицо явная аналогия с LDI и LDIR. А чтобы сия аналогия была более полной, расскажу о командах IND и INDR: ;пример команды IND IN (HL),(C) DEC HL DEC B ;пример команды INDR LOOP IND JR NZ,LOOP Вот и все сложности. Насколько мне ка- жется, проблем при освоении этих команд у вас возникать не должно. Но это были мутанты команды IN, а есть ещё и от OUT. Не вдаваясь в пустые рассуж- дения, сразу приведу их: OUTI, OUTD, OTIR, OTDR А учитывая то, что их действие до отвра- щения похоже на всё, о чём вы уже узнали, попробуем обойтись без излишних подробнос- тей. Из схем действия команд вам наверняка всё станет ясно: ;пример команды OUTI OUT (C),(HL) INC HL DEC B ;пример команды OTIR LOOP OUTI JR NZ,LOOP ;пример команды OUTD OUT (C),(HL) DEC HL DEC B ;пример команды OTDR LOOP OUTD JR NZ,LOOP Ну, что я говорил? Всё до безобразия по- нятно. Но если всё-таки вопросы возникнут, спрашивайте. По мере сил постараюсь на них ответить. Итак, с групповыми операциями мы разоб- рались. Осталось только, по давней нашей традиции, поведать... О влиянии групповых операций на флаги Как всегда, таблица нам в этом поможет. ╔═══════════╤═══════════════╗ ║ Мнемоника │ Флаги ║ ║ операции ├───────────────╢ ║ │ C Z P/V S N H ║ ╟───────────┼───────────────╢ ║ LDI │ . . x . 0 0 ║ ║ LDD │ . . x . 0 0 ║ ║ LDIR │ . . 0 . 0 0 ║ ║ LDDR │ . . 0 . 0 0 ║ ║ INI │ . x ? ? 1 ? ║ ║ IND │ . x ? ? 1 ? ║ ║ INIR │ . 1 ? ? 1 ? ║ ║ INDR │ . 1 ? ? 1 ? ║ ║ OUTI │ ? x ? ? 1 ? ║ ║ OUTD │ ? x ? ? 1 1 ║ ║ OTIR │ ? 1 ? ? 1 ? ║ ║ OTDR │ ? 1 ? ? 1 ? ║ ╚═══════════╧═══════════════╝ А вот теперь действительно всё... на се- годня. А вообще-то нам с вами ещё долго предстоит "Учиться, учиться, и учиться". Но это потом, а пока - до свидания! Продолжение следует... ──══════════──