Basics Of Machine Code - Part 2
-------------------------------
By L.A. Of Extacy-3
-------------------
Прежде чем начать, сразу скажу: если вы не читали часть 1 (в S.E.#1), вам обязательно нужно прочесть её, прежде чем браться за эту. Иначе весь материал покажется полной бессмыслицей.
Итак, ближе к делу. Попробовали ли вы исходник, который я давал в прошлом номере? Надеюсь, вам понравилось. Возможно, вы подумали, что многое там — ерунда и бессмыслица, но если вы не поймете и не усвоите такие элементарные вещи, у вас нет ни единого шанса научиться кодить!
В этом выпуске я решил разобрать организацию циклов и ещё несколько полезных команд.
Первым делом мы познакомимся с циклической командой DJNZ. Работает она с регистром B. Давайте напишем небольшой пример, а пояснения я дам чуть ниже:
LD B,69
POOCAKE DJNZ POOCAKE
RET
Итак, строка LD B,69 устанавливает наш счётчик на значение 69. DJNZ POOCAKE — вот тут начинается самое интересное. DJNZ приблизительно расшифровывается как «Decrement B and Jump if Not Zero» (уменьшить регистр B на единицу и перейти по адресу, если B не равен нулю). Поскольку мы загрузили в B число 69, команда будет повторяться в цикле до тех пор, пока B не станет равен 0. Но не забывайте: каждый раз при выполнении DJNZ регистр B уменьшается на единицу! Таким образом, после 69 проходов DJNZ регистр B станет равен 0. Тогда команда DJNZ просто завершит свою работу и процессор перейдёт к следующей инструкции, которой в нашем случае является RET. Подведём итог: мы заносим в B 69, DJNZ зацикливается на саму себя 69 раз, а затем происходит возврат (RETurn).
Понятно? Надеюсь, что да! Теперь давайте попробуем что-нибудь чуточку посложнее!
LD A,0
LD HL,16384
LD B,255
POOCAKE LD (HL),A
INC A
HALT
DJNZ POOCAKE
RET
Врубайтесь! Вы уже должны понимать приведённую выше программу (если вы, конечно, читали все мои предыдущие статьи!). Но на всякий случай, если кто-то всё ещё не до конца уловил суть, вот подробный разбор.
Мы загружаем в регистр A значение 0 (LD A,0). Затем загружаем в пару HL число 16384 (начало экранной области памяти, или Screen$). Затем в B (наш счётчик) загружается 255 (поскольку B — 8-битный регистр, его максимальное значение как раз 255). После этого мы пишем значение из A в ячейку памяти по адресу HL (LD (HL),A), что фактически означает команду POKE HL, A (в первый проход туда пишется 0). Затем значение A увеличивается на единицу (INC A). После этого процессору Z80 отдаётся команда HALT, заставляющая Спекки ждать прерывания (пауза на 1/50 долю секунды!). Затем следует переход DJNZ обратно на метку POOCAKE. Помните: DJNZ уменьшает B на единицу и делает прыжок, если B не ноль! Пока B отличен от нуля, программа будет возвращаться к строке LD (HL),A. Этот цикл повторится 255 раз — ровно столько, сколько мы изначально поместили в B. После завершения цикла выполняется RET. Из-за того, что цикл повторяется 255 раз, вы можете подумать: «Ого, 255 итераций — это займёт кучу времени!» На деле же это пролетит почти мгновенно, именно поэтому я и поставил внутрь команду HALT. Если для вас это всё равно слишком быстро — просто добавьте ещё команд HALT подряд после первой!
Другой способ организовать переход — использовать команду JP, что означает «JumP» (безусловный переход). Она вообще не зависит от регистра B (в отличие от DJNZ) или каких-либо других регистров. Процессор просто прыгает по указанному вами адресу. Это немного напоминает команду RANDOMIZE USR из Бейсика, но работает иначе. Дело в том, что когда вы выполняете RANDOMIZE USR, адрес возврата сохраняется в стеке (STACK — о нём я подробнее расскажу как-нибудь позже!), чтобы Спекки точно знал, куда возвращаться, когда дойдёт до команды RET. А вот при выполнении JP адрес возврата в стек не помещается. Поэтому, если вы сделаете RANDOMIZE USR из Бейсика на фрагмент машинного кода, который выполнит JP на другой кусок кода, оканчивающийся RET, то при достижении этого RET Спектрум сразу вернётся обратно в Бейсик! Он не станет прыгать назад к месту вызова JP (как это делает команда CALL — чёрт, я же ещё не объяснил CALL!).
Ну вот, так всегда. Я знал, что начну растекаться мыслью по древу и закончу разговором о командах, о которых вы, возможно, и не слышали вовсе.
CALL — команда предельно простая. Фактически это машинный эквивалент вызова RANDOMIZE USR (или GO SUB в Бейсике — прим. ред.). Когда вы делаете CALL по адресу, процессор переходит туда, а когда встречает RET, то возвращается к инструкции, следующей сразу за CALL. Допустим, у нас есть такой кусочек кода:
CALL POOCAKE
POOCAKE LD HL,16384
LD A,69
RET
Что, по-вашему, здесь произойдёт? Команда CALL POOCAKE перейдёт на метку POOCAKE и станет выполнять цепочку инструкций, пока не встретит RET. Когда команда RET выполнится, Спекки вернётся к следующей инструкции за CALL. А следующей здесь оказывается всё та же команда LD HL,16384! Спекки снова выполнит команды по порядку вплоть до RET, и вот теперь он уже вернётся обратно в Бейсик. Если бы вместо CALL стояла JP, то Спектрум прыгнул бы на POOCAKE, выполнил бы команды до RET и сразу вернулся бы в Бейсик (при условии, что изначально запуск был сделан через RANDOMIZE USR). А ведь именно так весь код обычно и запускается: вы загружаете его в память и делаете RANDOMIZE USR на стартовый адрес, запуская весь процесс.
Надеюсь, всё это было понятно. Если этот урок покажется вам сложным, перечитайте его ещё раз — не торопясь, кусочек за кусочком, пока всё не встанет на свои места!
Поделитесь вашим мнением о статье