Из газеты Optron #20, Львов, 07.11.98 Ассемблер: взгляд издалека Инфарх, 1998 --== Вступление ==-- Приветствую вас, уважаемые читатели Оптрона! Сегодня по вашим многочисленным заявкам мы начинаем цикл статей о том, что такое ассемблер и "с чем его едят". А "едят" его, сразу скажу, сначала с голов- ной болью, а потом с удовольствием. В самом деле, не зря ведь ассемблер яв- ляется главным языком программирования на Speccy. Ни один из других языков (Бейсик, Паскаль, Си...) не дает и десятой доли возможностей ассемблера. Программа на ас- семблере работает гораздо быстрее, занима- ет в памяти меньше места и предоставляет программисту гораздо больше простора для "издевательства" над машиной. Для начала хочу предупредить, что сей рассказ рассчитан на тех, у кого слова "система счисления" не вызывают непреодо- лимого желания попинать ногами труп учите- ля математики. И ещё я предполагаю, что вы уже знакомы с "Бейсиком". Ведь трудно на- чать с самого начала: всегда получается, что было что-то и до этого. Итак, присту- пим к знакомству. --== Что такое машинный код ==-- Процессор Z80 принимает сигналы с шины данных (ШД) и устроен таким образом, что разные комбинации этих сигналов предписы- вают этому процессору выполнить ту или иную функцию. Состояние одной линии ШД принято описывать в двоичной форме: "1" - есть сигнал, "0" - нет сигнала. Типичная команда машинного языка может выглядеть так: 001111000 Думаю, это несколько отличается от зна- комого по "Бейсику" LET A=A+1 Тем не менее, суть данной команды именно такова. Вот это и есть машинный код. Все необходимые комбинации единиц и нулей, при помощи которых осуществляется управление процессором, предварительно размещаются в памяти компьютера. Надо сказать, что у программ в кодах есть и свои недостатки: 1) Их сложно читать и отлаживать; 2) Они оказываются длиннее в командах; 3) Трудно выполнять арифметические действия. Очевидно, некоторые читатели уже тянут руку к сорок первой кнопке с мыслью о том, что этот язык - только для особо опасных маньяков, способных ввести в компьютер несколько десятков тысяч комбинаций единиц и нулей, при условии знания наизусть всех их назначений. И здесь вы совершенно правы. Редкий фа- натик может творить в таких условиях. Бо- лее нормальные люди используют АССЕМБЛЕР. --== Суть ассемблера ==-- Язык ассемблера и был придуман как раз для того, чтобы не стараться понять смысл программы вроде 00100001 00000000 01000000 и т.д. К счастью, для каждой подобной ком- бинации можно подобрать название, которое может быть прочитано и понято. Такие наз- вания называют мнемониками команд. Напри- мер, на данном этапе обучения конструкцию типа INC A вы можете и не понять, но по крайней мере прочитаете. А если сказать, что INC - стандартное сокращение от слова INCREMENT (последовательное увеличение/приращение; модификация суммированием), а "А" - пере- менная, то у вас будет уже некоторое представление о происходящем. Во всяком случае, "INC A" наверняка более понятно, чем "00111100". Между языком ассемблера и машинным кодом есть только одно различие: ассемблер на один уровень выше машинного языка. Челове- ку читать его легче, чем машинный код, но, с другой стороны, ЭВМ не может читать язык ассемблера. Он не является адаптацией машинного язы- ка, подобно "Бейсику", где для каждой опе- рации необходимо выполнение большого коли- чества команд процессора. В ассемлере каж- дой команде соответствует идентичная по назначению команда машинного языка. Для того, чтобы написать программу на ассемблере и привести ее в вид, понятный вашему Speccy, (т.е. машинный код), су- ществуют специальные программы, о которых далее и пойдет речь. --== Редакторы и мониторы ==-- Редактором ассемблера (далее - просто "Ассемблер") называется программа, при по- мощи которой вы можете ввести в компьютер текст программы на ассемблере и скомпили- ровать эту программу в машинный код, "по- нятный" процессору. Именно так команда "INC A" превратится в "00111100". На сегодня редакторов ассемблера сущест- вует множество, как для 48К-машин, так и для 128К. Вам самим предстоит выбрать из них тот, который подходит больше. Теперь о мониторах. Это слово означает некую программу, назначение которой - по- мочь вам пронаблюдать за тем, что происхо- дит в памяти компьютера во время выполне- ния вашей (и не только вашей) программы. О них мы поговорим подробнее несколько поз- же. А сейчас я попробую помочь вам выбрать себе Ассемблер, которым вы и будете поль- зоваться в дальнейшем. Рекомендую отнес- тись к этой теме внимательно, так как каж- дый Ассемлер хранит тексты программ в сво- ём уникальном формате. Перенос текста ва- шей программы (исходника) из одного редак- тора в другой бывает, порой, сопряжён с большими трудностями, и успех не всегда гарантирован. Меняя Ассемблер, вы рискуете лишиться всех ваших предыдущих работ. --== Что мы имеем? ==-- Для начала припомните объём памяти ваше- го Speccy. Правда, сейчас осталось уже очень мало 48-х машин, но все же... Для таковых - Ассемблеров гораздо меньше. В этом случае наблюдается редкое единодушие. Почти все рекомендуют использобать пакет "DEVPAC", куда входит Ассемблер "Gens" и мониторная программа "Mons". Такое сочета- ние (Ассемблер и монитор в "одном флако- не") предоставляет максимум удобств. "Mons", ко всему прочему, может дизассемб- лировать программу в машинных кодах, прев- ратив ее в исходный текст формата Ассемб- лера "Gens". Я сам использовал "DEVPAC" - и ни одного нарекания на его работу не имел. Однако дела обстояли таким образом лишь до того, как у меня появился Speccy-128. Здесь - совсем иначе. Ассемблеров очень много, и выбор свой я сделал не сразу. Пе- речислю некоторые, сказав несколько слов о каждом. TASM Этот редактор от версии к версии совер- шенствовался, поэтому формат текста версии 2 несовместим с последующими. Начиная с версии 3, реализована функция "Import 2.0" для преобразования формата. Версия 4.0 имеет в своем составе монитор "STS". Ас- семблер неплох, но несколько ограничен в возможностях, а шрифт 64 символа в строке не всегда удобен для чтения. О нем, равно как и о других программах, которые я упо- мяну, настоятельно рекомендую прочитать полное описание. MASM Очень похож на "TASM", как внешним оформлением, так и возможностями. Имеет в составе "STS" и функцию импорта из TASM 3. Радует то, что выбор файла для работы осу- ществляется не вводом его имени, а курсо- ром. Оба вышеуказанных Ассемблера (да и не только они), имеют в своем составе такие стандартные функции, как поиск в тексте фрагмента с возможностью замены, включение в ассемблирование текста ассемблера и ко- довых блоков с диска, ну и, конечно, ввод текста программы с последующим её ассемб- лированием. ALASM А вот это уже штука помощнее. В этом ас- семблере предусмотрена возможность наст- ройки на способ переключения страниц памя- ти вашей машины с помощью подгружаемых драйверов, написанных, опять-таки, в фор- мате "ALASM". Это даёт возможность рабо- тать с несколькими разными текстами однов- ременно. Также есть возможность изменить набор символов (фонт). Встроенный "STS", очевидно, уже стал стандартом. Но не обошлось и без недостатков. К та- ковым можно отнести невозможность компиля- ции текстов из нескольких страниц одновре- менно, сложности в синтаксисе и индикации ошибок. А самое главное - полученный после компиляции код нельзя записать на диск средствами самого Ассемблера. Для этого приходится использовать Бейсик или "STS". ZX-ASM Здесь реализован уже другой подход. Если в предыдущих Ассемблерах управление проис- ходило с помощью "горячих клавиш", то в "ZX-ASM" имеется обширная система меню. Но в то же время это составляет определённое неудобство, поскольку на выбор необходимой функции уходит иногда слишком много (во всяком случае, для меня) времени. XAS В целом оставляет очень приятные впечат- ления. Листание текста происходит довольно быстро, ускорены дисковые операции. Можно работать с текстами в двух страницах. При- чём, подгрузка текста при ассемблировании происходит во вторую страницу, тем самым появляется возможность обработать за раз до 30 Кb текста. Имеется и перекодировщик (отдельным файлом) для конвертации текстов других форматов в "XAS". Еще одна страница выделена для макросов, которые можно в лю- бой момент отредактировать. Есть возмож- ность сокращённого набора и автотабуляция. Ложка дёгтя: невозможность замены при поиске в тексте, нет приоритетов в арифме- тике. Ещё нет функции "Merge", но это оку- пается возможностью переноса текста из страницы в страницу. STORM Листание текста тут происходит... ну очень быстро! Это относится и к ассембли- рованию. Сокращённый набор и автотабуля- ция, правильный приоритет в арифметических операциях, а также очень быстрый поиск в тексте. К недостаткам можно отнести непривычное управление и невозможность работы одновре- менно с несколькими текстами. STS Ну, вот мы и до него добрались. Как уже говорилось, это мониторная программа. И скажу ещё, что на данный момент замены ей не найдешь, ибо "STS", в буквальном смысле слова, незаменим для 128-х машин. С его помощью вы можете контролировать состояние всех ресурсов машины в процессе пошагового выполнения (трассировки) программы в ма- шинных кодах. При этом на экране вы наблю- даете полноценный дизассеблер, а не двоич- ные числа. От версии к версии "STS" совершенство- вался, обрастая новыми функциями. Есть версия 3.2 с возможностью просмотра блока данных как графики. А версия "STS 4.3+@" предоставит возможность удобно работать с MAGIK-файлами. Короче говоря, берите и пользуйтесь. Не пожалеете! --== В заключение... ==-- Вот и подошел к концу вводный курс. Надо ещё добавить, что преимущества и недостат- ки Ассеблеров я описал далеко не все, но это уже - для самостоятельного изучения... Осталось вам подобрать себе подходящий инструмент и дождаться продолжения этой статьи, в котором мы попробуем разобраться уже конкретно с языком ассемблера. Все описанные программные продукты вам поможет найти наша редакция. При этом до- бавлю, что без ваших писем и звонков мы не сможем дать той информации, которая вам действительно нужна! Координаты, как редакции, так и мои собственные, вы сможете найти на страничке "About". До встречи!!! Из газеты Optron #21, Львов, 28.11.98 Ассемблер: взгляд издалека Инфарх, 1998 Продолжение. Начало в №20 Итак, уважаемые читатели, продолжим наши занятия. Надеюсь, вы уже обзавелись Ас- семблером и прочли инструкцию к нему. Впрочем, сегодня это не столь важно, а вот когда теорию подтянем... Но давайте по порядку. Сейчас предмет нашего рассмотрения - Представление чисел Вы наверняка уже привыкли, что в двоич- ной форме числа (в пределах байта) бывают только положительные: от 0 до 255. Но в действительности значение того или иного числа зависит от того, как его интерпрети- ровать. Возможно даже использование чисел определенного диапазона как отрицательных. Вы сами можете выбрать, в каких пределах лежит ваше число: 0...255 или -128...+127. Как же должно выглядеть отрицательное число? Очевидно, оно должно давать нулевой результат при сложении со своим положи- тельным эквивалентом. Поэтому для получе- ния числа с противоположным знаком необхо- димо: 1) Инвертировать все биты числа; 2) Увеличить его на единицу. "А есть ли другие виды представления чи- сел?" - спросите вы. Да, есть. Это - не так часто используе- мая, но тем не менее иногда полезная - двоично-десятичная форма. В ней числа бы- вают от 0 до 99. Байт делится на полубайты (4 бита). Каждый из них принимает значения 0...9. Вычисления с примененем такой формы более сложны, но не отчаивайтесь! В свое время это будет рассмотрено. Регистры центрального процессора Теперь поговорим о том, что можно найти внутри Z80, если как следует там поковы- ряться. Вообще-то, там много интересного, но пока нам нужны только занятные предме- ты, в просторечии именуемые РЕГИСТРЫ. Предназначены они для того же, что и обык- новенные, известные по Бейсику, перемен- ные, а именно - для хранения информации и безобразных издевательств над оной. Регистр обозначается буквой латинского алфавита. Для регистров общего назначения процессора Z80 используются буквы: A B C D E H L Самым главным является регистр А, назы- ваемый "аккумулятором". С ним прoизводятся все арифметические и логические операции, в нём же остаётся их результат. Вмещает регистр один байт информации. Но ведь иногда необходимо работать и с боль- шими значениями! Выход есть: регистры мо- гут объединяться в пары, соответственно увеличивая свою ёмкость до одного слова (2 байта). Объединение происходит отнюдь не произвольно, а в весьма конкретной фор- ме. Вот возможные варианты регистровых пар: BC DE HL Главной регистровой парой является HL. Она, как и аккумулятор, необходима почти во всех операциях. Кстати, вас, наверное, удивило, что для "А" пары не нашлось? На самом деле есть и она, но регистр-пара к "А" - вовсе не общего назначения, поэтому его мы пока изучать не будем. Скажу лишь, что имя его - "F", и образует он пару "AF". Ещё есть служебные регистры - "I" и "R". Они не объединяются в пары, а существуют сами по себе. Ещё есть пары, которые на отдельные ре- гистры не делятся (точнее, делятся, но особо извращёнными методами, их упоминать пока рано). Вот их имена: IY IX А ещё есть шестнадцатиразрядные, не де- лимые на пары, регистры: SP PC Думаете, это всё? А вот и нет! Ещё есть альтернативные регистры: A' F' B' C' D' E' H' L' Они, соответственно, образуют пары: AF' BC' DE' HL' Работа с регистрами общего назначения Если мы хотим совершать какие-либо дейс- твия над регистрами, то для начала надо научиться придавать им конкретные значе- ния. Для этой цели используется команда ассемблера с мнемоникой "LD", которая яв- ляется сокращением английского слова Load (загрузить). Надо сказать, что команда эта - не только для регистров, она ещё и для ячеек памяти может пригодиться. Вот воз- можные варианты её применения: LD r,r LD r,(HL) LD r,n LD r,(ii+n) LD (HL),r LD (ii+n),r LD (HL),n LD (ii+d),n LD A,(BC) LD A,(DE) LD A,(nn) LD (BC),A LD (DE),A LD (nn),A LD rr,nn LD ii,nn LD HL,(nn) LD rr,(nn) LD ii,(nn) LD (nn),HL LD (nn),rr LD (nn),ii LD I,A LD R,A LD A,I LD A,R Расшифруем использованные сокращения: r = A, B, C, D, E, H, L rr = BC, DE, HL, SP ii = IX, IY n = байт [#00..#FF] nn = слово [#0000..#FFFF] Зная, что может команда "LD", остаётся уточнить, что означают некоторые конструк- ции, например, "(nn)" и "rr". А это - все- го лишь байт, на адрес которого указывает слово "nn" или содержимое регистровой пары "rr". Или вот "(ii+n)". Тоже просто: адрес байта определяется сложением содержимого регистра "ii" и байта "n". Это удобно при большом количестве переменных, сведённых в таблицы. В этом случае достаточно занести адрес начала таблицы в "ii", а конкретный элемент получать, задавая в команде смеще- ние. Кстати, здесь есть одна тонкость. Та- ким методом возможна адресация в диапазоне не (ii)...(ii+255), а (ii-128)...(ii+127). При использовании команд загрузки, равно как и прочих команд процессора, помните одно правило: пересылка происходит в на- правлении от второго параметра команды в первый. Иными словами, команды "LD A,5" и "LD C,A" означают, что в регистр "А" поме- щается число 5, а затем его содержимое пе- ресылается в регистр "В". Регистр "F" называется "флаговым". Его биты (флаги) - каждый по отдельности - служат для индикации результатов операций, выполняемых процессором. Шестнадцатиразрядный регистр "SP" указы- вает на вершину машинного стека - область памяти, предназначенную для хранения неко- торой информации. Но это - отдельная исто- рия. Регистр "PC" является программным счёт- чиком. Число, хранящееся в нём, является адресом команды, исполняемой в текущий мо- мент. Помните! На начальном этапе обучения, во избежание кошмарных глюков, зависаний и прочего, не используйте для опытов ре- гистры SP, PC, HL'. В случае с IY старай- тесь не менять содержимое адресуемых им ячеек памяти и собственно его содержимое. Работа с альтернативными регистрами Эти регистры включаются вместо основных с помощью команды ассемблера EXX Её мнемоника происходит от слова "ex- change" (обмен). При выполнении команды происходит обмен HL <=> HL' DE <=> DE' BC <=> BC' Для включения альтернативной пары AF' служит команда с мнемоникой EX AF,AF' Вообще-то, нет никакой возможности узнать, какой набор регистров активен в данный момент. Процессор тоже не в курсе - ему это "фиолетово". Так что считайте альтернативным тот набор, который в данный момент не активен. Арифметика Здeсь у Z80 возможности несколько огра- ничены. Он может производить только сложе- ние и вычитание. Вот некоторые варианты этих действий: ADD A,r ADD A,(HL) ADD A,n ADD A,(ii+n) ADD HL,rr ADD IX,ry ADD IY,rx SUB r SUB (HL) SUB n SUB (ii+n) ADD означает сложение, а SUB - вычита- ние. Результат вычисления помещается в ре- гистр, который в команде стоит первым. Но не особенно старайтесь его запомнить. Если вычисления производятся над однобайтным числом, то первым всегда будет "А" (обра- тите внимание: в этом случае команда SUB не требует первого операнда). А если про- изводятся операции над словом, то в начале будет HL, IX или IY. Флаги Перечислим все возможные флаги регистра "F": D7 D6 D5 D4 D3 D2 D1 D0 S Z * H * P/V N CY Флаг S (sign) устанавливается, если при выполнении арифметической или логической операции получился отрицательный ре- зультат. Флаг нуля Z (zero) устанавливается, если в результате выполнения арифметической или логической операции получился нулевой ре- зультат. Флаги H (half-carry) и N (negative) ис- пользуются весьма редко - в основном, при работе с двоично-десятичными числами. Флаг чётности/переполнения P/V (parity/ overflow) - единственный, на который воз- ложена индикация сразу двух условий. Флаг чётности устанавливается, если в результа- те логической операции в байте оказалось чётное количество установленных в "1" бит. Флаг переполнения показывает, что после арифметического действия изменился знак операнда. Флаг переноса CY (carry) говорит, что в результате арифметической операции произо- шёл перенос из старшего разряда. Например, в случае: 253 + 5 = 258. Разрядов регистра не хватит для сохранения такого результа- та. Поэтому реально мы получим число 2 и установленный флаг CY. К сему добавлю, что далеко не все коман- ды процессора влияют на флаги, и, кроме того, отдельные команды изменяют только некоторые биты флагового регистра. Назад к арифметике А теперь, узнав о флагах, можно перейти к арифметическим операциям, учитывающим перенос: ADC A,r ADC A,(HL) ADC A,n ADC A,(ii+n) ADC HL,rr SBC A,r SBC A,(HL) SBC A,n SBC A,(ii+n) SBC HL,rr В качестве примера рассмотрим такую си- туацию: надо прибавить к "А" некое число. Никаких проблем не возникнет, пока ре- зультат можно описать одним битом. Однако всё усложнится (хоть и не намно- го) в такой, например, ситуации: А = 200 n = 57 Естественно, 200 + 57 = 257. Но в одном байте это не уместится. Поэтому там сохра- нится число 1, и установится флаг CY. Вро- де, пока всё понятно, и используемая ко- манда никаких сомнений не вызывает: ADD A,57 Но, если следующая команда будет из группы ADD или SUB, то содержимое CY будет потеряно, так что не забывайте о группах ADC и SBC! --==========-- Ознакомившись со всем вышеизложенным и, вооружась выбранным вами ассемблером, по- экспериментируйте - и вы поймете, что в этом что-то есть! Но не забывайте: любая ассемблерная программа должна заканчи- ваться командой RET Итак, дерзайте! И до новых встреч. Из газеты Optron #24, Львов, 23.02.99 Ассемблер: взгляд издалека {}Инфарх Продолжение. Начало - в №№ 20, 21 Логические операции Разобравшись в прошлый раз с арифмети- ческими операциями, перейдём к операциям логическим. В отличие от Бейсика, в Ас- семблере они столь же важны. Тем, кто не знаком с алгеброй логики, скажу, что её главное отличие от обыкно- венной арифметики состоит в том, что в ней действия производятся не над всем числом, а над его битами - т.е. перенос из разряда в разряд не производится. Процессор Z80 способен совершать следую- щие логические операции: AND OR XOR NOT Но не ищите в таблицах мнемоник команду NOT - она записывается как "CPL". А разобраться в назначении каждой логи- ческой операции нам поможет таблица истин- ности. Операция AND +-----------+ | AND | +---+---+---+ | A | B | Y | +---+---+---+ | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 | +---+---+---+ Символами A и B обозначены операнды, а Y - это результат. В английском языке "AND" означает "И". В качестве микросхемы, выполняющей эту функцию, можно привести 555ЛИ1. Суть операции "И" (логического умноже- ния) можно уяснить из следующего примера. Обозначим прочтение вами моей статьи че- рез Y = 1, а непрочтение - через Y = 0. Для получения результата вам необходимо выполнить две операции: - загрузить в ваш Спекки "Оптрон" |24 (если да, то A = 1, а если нет, то A = 0); - найти в "Оптроне" рубрику ЛИКБЕЗ (со- ответственно, B = 1 или B = 0). Итак, для получения в качестве результа- та логической "1" оба операнда также должны быть равны логической "1". Вот уравнение этой операции: Y = A*B Операция OR "OR" в переводе означает "ИЛИ". Эту опе- рацию называют также "логическое сложени- е". Снова обратимся к примеру. Чтобы ознакомится с моими текстами в "Оптроне" |24 (т.е. получить Y = 1), вам необходимо выбрать рубрики ЛИКБЕЗ (A = 1) или ЛИТСТРАНИЧКА (B = 1). Ясно, что для получения положительного результата, еди- нице должен быть равен или А, или В, или оба вместе. Вот - таблица истинности для этой опера- ции: +-----------+ | OR | +---+---+---+ | A | B | Y | +---+---+---+ | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 1 | +---+---+---+ А вот - её уравнение: Y = A + B А пример из микросхемотехники - 555ЛЛ1. Операция XOR Это - ИСКЛЮЧАЮЩЕЕ ИЛИ. Снова привожу пример. Если вы вздумаете, читая мою статью, ОД- НОВРЕМЕННО поиграть в "Чёрный Ворон", то я скажу: "Нет, голубчики, за двумя зайцами пого- нишься - ни одного не поймаешь! Y = 0!" Соответсвенно - и таблица истинности: +-----------+ | XOR | +---+---+---+ | A | B | Y | +---+---+---+ | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | +---+---+---+ В обычной арифметике эта операция из- вестна как "суммирование по модулю 2": 1 + 1 = 0 Точно так же в десятичной системе счис- ления при "суммировании по модулю 10": 9 + 1 = 0 (При "сложении по модулю" перенос "оста- ётся в уме"). Операция NOT "NOT" означает "НЕ". Эта операция - не простая, а очень простая. Она производится только над одним операндом и меняет его значение на противоположное: +-------+ | NOT | +---+---+ | A | Y | +---+---+ | 0 | 1 | | 1 | 0 | +---+---+ Для любителей уравнений: _ Y = A Для "железячников": 555ЛН1. Перейдем к мнемоникам Воздействие арифметических операций на флаговый регистр, напомню, вопросов не вы- зывает... Но на то она и логика, чтобы мозги процессору парить! Так что теперь, рассматривая варианты мнемоник, будем ука- зывать влияние логических операций на фла- ги: +-------------+-------------------+ | МНЕМОНИКА |Разряды регистра F | | | (флаги) | +----+--------+-------------------+ |Опе-| | | | ра-|Операнд | C Z P/V S N H | | ция| | | +----+--------+-------------------+ | AND r | 0 x P x 0 1 | | AND (HL) | | | AND n | | | AND (ii+n) | | | | | | OR r | 0 x P x 0 0 | | OR (HL) | | | OR n | | | OR (ii+n) | | | | | | XOR r | 0 x P x 0 0 | | XOR (HL) | | | XOR n | | | XOR (ii+n) | | | | | | CPL | . . . . 1 1 | +-------------+-------------------+ Пояснения: Операции ВСЕГДА производятся над операн- дом, указанным в таблице, и содержимым ак- кумулятора; "." - флаг не меняется в результате опе- рации; "x" - флаг устанавливается в зависимости от результата операции; прочие обозначения - см. |21. Результаты действия команд (примеры) При выполнении команды AND E имеет место операция "И" над содержимым регистров "А" и "Е". Допустим, они имеют следующие значения: A = 10011010 E = 01111001 В этом случае мы, согласно таблице ис- тинности для AND, получим: 1 0 0 1 1 0 1 0 AND 0 1 1 1 1 0 0 1 --------------- 0 0 0 1 1 0 0 0 Результат "00011000" заносится в аккуму- лятор. "E" своего значения не меняет. Точно так же при выполнении команд OR и XOR с теми же значениями операндов получим: 1 0 0 1 1 0 1 0 OR 0 1 1 1 1 0 0 1 --------------- 1 1 1 1 1 0 1 1 1 0 0 1 1 0 1 0 XOR 0 1 1 1 1 0 0 1 --------------- 1 1 1 0 0 0 1 1 Команда "CPL" (операция "NOT", не забы- ли?) производится над одним операндом - содержимым регистра "A". Вот что будет после её выполнения: 1 0 0 1 1 0 1 0 CPL --------------- 0 1 1 0 0 1 0 1 С логическими операциями - всё. Продол- жение следует. Кстати, о логических операциях... {}Tertius Gaudens, 1999 Он думал, что на потолке Сидит Большой Паук; Протёр глаза, а перед ним - Разгадка Всех Наук; "Учение, - подумал он, - Не стоит этих мук!" Льюис Кэрролл. Песня садовника (перевод Гр.Кружкова) Данная статья написана в дополнение к рубрике "Ликбез" этого номера. Честь открытия функции НЕ принадлежит древнегреческому философу-софисту Евбулиду из Милета (IV в. до н.э.) Если его знаме- нитую апорию "Лжец" ("Правда состоит в том, что я лгу") выразить графически, то получится жорошо всем знакомый синхрогене- ратор на инверторе: +---+ 1 +-+ +-+ +-+ +-+ +-+ +-+ 1 o-+- 0 -+ +-+ +-+ +-+ +-+ +- | |561| | | |ЛН2| | | +---+ | +-------+ (Надо только добавить задержку на пере- ход из состояния в состояние). Главным же основателем ЛОГИКИ ("науки об умозаключениях" - древнегреч.) считается Аристотель (384 - 322 гг. до н.э.) В основу логики Аристотеля положен прин- цип, согласно которому всем операндам присваиваются некие определения ("Сократ есть человек, умный, хороший"; "Яблоко - фрукт, круглый, сладкий"; "Водка - нектар, божественный"). Все дальнейшие логические операции производились на основе этого принципа. Вот эту-то насвозь субъективную и неоднозначную "логику определений" ("предикатов") и вешали на уши челове- честву более двух тысяч лет - пока не спохватились, что в двоичной форме её не очень-то выразишь. А между тем логика, полностью отвечающая требованиям двоичного кодирования, сущест- вовала! Её автором был основатель древне- греческой же философской школы стоиков Хрисипп (280 - 208 гг. до н.э.) Принципи- альное отличие его "логики высказываний" от предикативной состояло в том, что ВСЕ - независимо от их происхождения - операнды могли принимать только 2 значения - "Прав- да" и "Ложь". Обозначив понятия "Правда" и "Ложь" че- рез, соответственно, логические "1" и "0", философ-позитивист Джон Буль (Англия, 1815 - 1864) создал булеву алгебру, которая до сих пор применяется в вычислительной тех- нике для логических операций. Непременным атрибутом этой алгебры являются таблицы истинности. Соотечественник и единомышленник Буля - Август де Морган (1806 - 1871) - ввёл по- нятие "совершенной нормальной формы" (ба- зового логического элемента - БЛЭ - при помощи которого можно реализовать любую операцию булевой алгебры): ___ _ _ A*B = A + B _____ _ _ A + B = A*B Основатель философии прагматизма Чарльз Пирс (США, 1839 - 1914) предложил приме- нять в качестве БЛЭ свою "стрелку Пирса" (сейчас известную, как операция ИЛИ-НЕ и микросхема 555ЛЕ1): +-----------+ | OR-NOT | +---+---+---+ | A | B | Y | +---+---+---+ | 0 | 0 | 1 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | +---+---+---+ _____ Y = A + B Стрелка Пирса является БЛЭ в диод- но-транзисторной логике. Как, по вашему, список философов этим исчерпан? Конечно, нет! В ТТЛ в качестве БЛЭ применяется "штрих Шеффера" (И-НЕ, 555ЛА3): +-----------+ | AND-NOT | +---+---+---+ | A | B | Y | +---+---+---+ | 0 | 0 | 1 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | +---+---+---+ Y = A/B = A*B Для Z80 БЛЭ - это операция XOR ("исклю- чающее ИЛИ", "суммирование по модулю 2", "отрицание эквивалентности", "операция Же- галкина", 555ЛП5), введённая в оборот марксистом Иваном Жегалкиным (Россия, 1869 - 1947): Y = A M2 B _ ______ _ _ Y = A M2 B = A*B + A*B Читателям "Оптрона" будет небезынтересно узнать, что существенный вклад в матлогику (раздел "семантика") внесли представитель т.н. "львовско-варшавской философской шко- лы" Альфред Тарский (1902 - ?) и митропо- лит Андрей Шептицкий (1863 - 1944) - каж- дый по своему, разумеется. А окончательно матлогику, как отдельную науку, сформировал основатель философии неопозитивизма Бертран Рассел (Англия, 1872 - 1970). Кстати, именно в Англии наиболее явно проявилась теснейшая связь математики, фи- лософии и художественной литературы. Выше- упомянутый Дж.Буль - отец писательницы Э.Войнич. Основательница теории программи- рования А.Лавлейс - дочь поэта Дж.Байрона. А как насчет эвристического компьютера Дж.Свифта ("Путешествие Гулливера в Лапу- ту")? О лимериках Э.Лира и Л.Кэрролла и их роли в создании неклассической матлогики я уж и не говорю... И каков же вывод из всего этого? Хотите познать программирование - изучайте фило- софию и художественную литературу, друзья мои! Из газеты Optron #25, Львов, 20.03.99 Ассемблер: взгляд издалека {}Инфарх Продолжение. Начало - в №№20, 21, 24 Работа с битами Логические операции, рассмотренные нами в прошлый раз, производились над всем бай- том. Теперь обратим внимание на команды, работающие с отдельными битами. Ну, что с ними можно делать? Очевидно, присваивать им конкретное значение, проверять состоя- ние и... всё! Начнём с установок. Команда, которая установит конкретный бит, записывается, как "SET" (от англ. "установить"). А параметрами её служат указания на обрабатываемый байт и бит, подлежащий установке. Как водится, при- ведём варианты написания этой команды: SET b,r SET b,(HL) SET b,(ii+n) Как Вы думаете, что такое "b"? Да, со- вершенно верно! Это номер того бита, кото- рый мы мучаем командой. А нумеруются эти биты от младшего разряда к старшему. Вот, посмотрите: +----+----+----+----+----+----+----+----+ | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | +----+----+----+----+----+----+----+----+ Таким образом, если процессор встретит невзначай команду SET 4,E то в разряде "D4" регистра "E" окажется единица. Теперь на очереди у нас команда "RES". Нетрудно догадаться, что её действие прямо противоположно команде "SET", и зак- лючается оно в сбросе заданного бита. Вот варианты её написания: RES b,r RES b,(HL) RES b,(ii+n) Итак, мы умеем уже устанавливать и сбра- сывать биты. Но есть ещё одно, очень важ- ное, действие. Оно заключается в проверке бита, и у Z80 такая команда, конечно, есть. Её мнемоника - "BIT". А результат её вы- полнения мы получим в виде флага "Z", ко- торый установится, если проверяемый бит равен нулю или, выражаясь точнее, сброшен. А вот и возожные варианты: BIT b,r BIT b,(HL) BIT b,(ii+n) Как видите, варианты написаний всех рассмотренных выше команд абсолютно одина- ковы. Нам остаётся только вспомнить, что при выполнении команды возможно её влияние на флаги и посмотреть, в чём оно выражает- ся... +-------------+-------------------+ | МНЕМОНИКА |Разряды регистра F | | | (флаги) | +----+--------+-------------------+ |Опе-| | | | ра-|Операнд | C Z P/V S N H | | ция| | | +----+--------+-------------------+ | BIT b,r | . x ? ? 0 1 | | BIT b,(HL) | | | BIT b,(ii+n)| | | | | | RES b,r | . . . . . . | | RES b,(HL) | | | RES b,(ii+n)| | | | | | SET b,r | . . . . . . | | SET b,(HL) | | | SET b,(ii+n)| | +-------------+-------------------+ Пояснения: "." - флаг не меняется в результате опе- рации; "x" - флаг устанавливается в зависимости от результата операции; "?" - значение флага не определено; прочие обозначения - см. |21. Из таблицы видно, что только команда "BIT" оказывает воздействие на флаги, а прочие команды оставляют их без изменений. Ну, а коль скоро речь зашла о проверке бита, уместно вспомнить и методы контроля всего байта. Итак... Сравнение чисел В создаваемой программе вам наверняка понадобится производить сравнение неких чисел для последующего управления процес- сами в этой самой программе. Но как сравнить числа и получить ре- зультат в форме, понятной процессору? И что означает выражение "числа совпадают"? А значит это, что они равны. Если сей факт выразить арифметически, то это будет A - B = 0 А если сравниваемое с A число больше (ведь сравнение предполагает отношение между двумя числами)? Тогда результат вы- читания будет отрицательным. А если меньше, то положительным. А теперь на основе команды вычитания SUB попробуем разработать систему сравнения на Ассемблере. Допустим, необходимо сравнить число не- которого диапазона с числом 16: LD A,16 ;имеющееся число SUB n ;сравниваемое число Тогда получится следующее: - если n = 16, то флаг нуля (Z) установ- лен, а флаг переноса (CY) - сброшен; - если n < 16, то флаги нуля и переноса сброшены; - если n > 16, то флаг нуля сброшен, а флаг переноса установлен. Как видите, метод вполне работоспособен. Однако, есть у него и недостаток: измене- ние содержимого регистра "A". И вот здесь к нам на подмогу приходит... Команда сравнения Она имеет мнемонику "CP" (сокращение от английского "compare" - сравнить). Команда полностью аналогична вычитанию, за исклю- чением того, что содержимое "A" не изменя- ется. Результат сказывается только на фла- гах. Траиционно приведём возможные варианты: +-------------+-------------------+ | МНЕМОНИКА |Разряды регистра F | | | (флаги) | +----+--------+-------------------+ |Опе-| | | | ра-|Операнд | C Z P/V S N H | | ция| | | +----+--------+-------------------+ | CP r | x x V x 1 x | | CP (HL) | | | CP n | | | CP (ii+n) | | +-------------+-------------------+ Пояснение: Флаг P/V (parity/overflow) индицирует сразу два разных условия, относящихся к двум принципиально разным группам команд. При выполнении операции сравнения наличие флага Overflow показывает, что в результа- те арифметического действия изменился знак операнда. А в завершение взгляните на небольшую таблицу: она поможет вам выбрать, какие операнды в каких регистрах следует размес- тить для наиболее оптимального использова- ния команды сравнения. +----------+-----------+----------+ |Результат | Состояние | Мнемоника| |сравнения | флагов | условия | +----------+-----------+----------+ | A=X | Z=1 | Z | +----------+-----------+----------+ | A<>X | Z=0 | NZ | +----------+-----------+----------+ | Беззнаковое сравнение (0...255) | +----------+-----------+----------+ | A=X | CY=0 | NC | +----------+-----------+----------+ | С учетом знака (-128...+127) | +----------+-----------+----------+ | A=X | S=0 | M | +----------+-----------+----------+ Пояснения: 1) Таблица показывает, какие флаги уста- новятся при равенстве или отличии операн- дов в любую сторону. 2) В графе "Мнемоника условия" описыва- ются проверяемые флаги при вводе команд условного типа. 3) Обозначения в таблице: CY=C=Carry NZ=(Z=0) NC=(C=0) Должен заметить, что я, например, пока такую таблицу перед глазами не повесил, всё время в условиях путался. До новых встреч! Из газеты Optron #28, Львов, 09.06.99 Ассемблер: взгляд издалека. Продолжение. Начало в NN20, 21, 24, 25 (C) Инфарх, 1999 Команды единичного приращения В самой первой статье нашего "Ликбекза" как пример команды Ассемблера упоминалась команда приращения INC. Настало время вплотную заняться ею, а также её "напарни- цей" - командой DEC (от англ."decrement" - многократное уменьшение значения перемен- ной на заданную постоянную величину). Никак нельзя приуменьшить их важности! Ведь если вам вдруг понадобится увеличить или уменьшить содержимое некоего регистра или регистровой пары на единицу, то что делать: работать с арифметическими действиями? Упаси Господь! И здесь нам на помощь приходят эти самые команды положительного и отрицательного единичного приращения. Рассмотрим варианты их использования, не забыв и о флагах: +-------------+-------------------+ | МНЕМОНИКА |Разряды регистра F | | | (флаги) | +----+--------+-------------------+ |Опе-| | | | ра-|Операнд | C Z P/V S N H | | ция| | | +----+--------+-------------------+ | DEC r | . x V x 1 x | | DEC (HL) | | | DEC (ii+n) | | | DEC rr | . . . . . . | | DEC ii | | | | | | INC r | . x V x 0 x | | INC (HL) | | | INC (ii+n) | | | INC rr | . . . . . . | | INC ii | | +-------------+-------------------+ Обозначения - аналогично предыдущим статьям "Ликбеза". Хоть команда и предельно проста, рассмо- трим пример. Перед вами - фрагмент прог- раммы: LD A,5 LD HL,#C000 LD (HL),A INC HL DEC A LD (HL),A Первые три строки понятны: в "А" записы- вется число 5, в регистровую пару "HL" - число #C000. Затем происходит пересылка содержимого "А" по адресу (HL), после чего содержимое байта по адресу #C000 равно пя- ти. Следующая строка содержит команду "INC HL", после выполнения которой в "HL" будет уже число #C001. Соответственно, после "DEC A" в аккумуляторе окажется уже не пя- тёрка, а четвёрка, которая следующей ко- мандой будет переслана по адресу (HL) - #C001. Вот так можно легко задавать прира- щения для регистров, регистровых пар и байтов, адресуемых через "HL", "IX" или "IY". Продолжим воспоминания Из наших предыдущих занятий у вас могло создаться превратное впечатление, что про- цессор Z80 выполняет свои команды последо- вательно одну за другой без всяких прочих вариантов. Но это далеко не так! Конечно, команды следуют своей чередой. Но что управляет этим процессом? Припомните прошлые занятия - регистр PC, он же - программный счётчик. В нём сохра- няется адрес той команды, которая должна выполняться. Процессор, обрабатывая опре- делённую команду в ячейке памяти, адресуе- мой через PC, автоматически увеличивает его на единицу. Таким образом, содержимое PC постоянно модифицируется в процессе ра- боты программы. А если мы захотим изменить последова- тельность выполнения команд в программе, то нам достаточно изменить содержимое PC. И сделать это можно при помощи манипуляций с битами и проверки состояния бита и бай- та, о чём в прошлый раз у нас и шла речь. Пора применить полученные знания в деле. Итак... Прямой переход Есть у процессора такая команда, как JP (от англ. jump - прыжок, переход). За ней следует двухбайтный параметр: адрес, на который мы хотим сделать переход. Когда процессор встретит команду JP, он возьмёт следующий за ней адрес, поместит его в PC и... перейдёт к выполнению следующей ко- манды - той, чей адрес он только что полу- чил. Адрес перехода не обязательно должен на- ходиться непосредственно в памяти следом за JP. С не меньшим успехом им может быть содержимое регистровых пар HL, IX, IY. Ко- манда записывается следующим образом: JP nn JP cc,nn JP (HL) JP (ii) - А что это за "cc" такое? - можете вы спросить. А это - ни что иное, как условие. Да, команда перехода может быть не только бе- зусловной! Вспомните таблицу из прошлого нашего урока: была там такая колонка, как "мнемоника условия". Вот её мы здесь и применяем. Например: Адрес Команда #6000 JP C,#C003 #6003 JP (HL) Представим себе, что в PC записано число #6000 и процессор приступает к выполнению команды. По ходу дела PC увеличится на три (именно столько байт занимает команда JP C,#C003). В процессе этого будет прове- рено, выполняется ли условие "C". А оно выполняется, если CY=1. После выполнения команды Z80 помещает слово #C003 в PC и начинает выполнять следующую команду, ко- торая теперь уже находится по адресу #C003. А если условие не выполнено (CY=0)? Тог- да процессор никаких новых данных помещать в PC не будет, и следующая команда, кото- рую Z80 будет выполнять, расположена по адресу #6003. И команда эта представляет собой всё тот же переход, но теперь ника- ких условий проверяться не будет, в PC бу- дет переписано содержимое "HL", туда же будет и переход. Упомянув условие "С", скажем и oбо всех остальных. Вот какие мнемоники условий можно использовать в команде "JP cc,nn": C, NC, Z, NZ, M, P, PE, PO. Сложного ничего в них нет. Рассмотрим их все по порядку: Z - ноль (флаг Z установлен) NZ - не ноль (флаг Z сброшен) C - перенос (флаг C установлен) NC - нет переноса (флаг C сброшен) M - отрицательный результат (флаг S уста- новлен) P - положительный результат (флаг S сбро- шен) PE - чётность или перепонение (установлен флаг P/V) PO - нет чётности/переполнения (флаг P/V сброшен) А теперь осталось обратить внимание на то, что условным может быть только переход по непосредственно указанному адресу, а если адрес находится в регистровй паре, то переход может быть только безусловным. Относительный переход Этот переход отличается от прямого тем, что в команде задаётся не прямой адрес, а смещение, которое процессор не записывает в PC, а прибавляет к нему. Причём в данном случае смещение может быть представлено как положительным, так и отрицательным числом (вспомните урок в "Оптроне" N21), так что переход может быть осуществлён в любом направлении от команды. Записывается команда так: JR e JR cc,e Мнемоника JR происходит от английского "jump relative", что и означает "Относи- тельный переход". Обратите внимание на параметр команды: "e". Он означает величину смещения относи- тельно содержимого PC. Как видите, и эта команда может быть условной. Правила здесь действут аналогичные "JP", но вот набор возможных условий несколько меньше. Прове- ряются только C, NC, Z, NZ. Рассмотрим механизм действия команды JR в условном варианте: Адрес Команда #6000 JR NC,2 #6002 LD A,5 #6004 ....... Итак, Z80 приступает к выполнению коман- ды по адресу #6000. Поскольку она двух- байтная, PC будет увеличен на 2 и после выполнения команды будет равен #6002. В процессе выполнения команды процессор про- веряет условие NC на истинность. Теперь, если CY=0, то к программному счётчику бу- дет прибавлен ещё и параметр команды JR (в данном случае 2), операция "LD A,5" будет пропущена, и следующей выполняемой коман- дой будет та, что находится по адресу #6004. А какой вообще смысл в такой команде? Чем она отличается от JP? Всё очень прос- то. Ведь команда JP оперирует с прямым ад- ресом и занимает в памяти три байта. Но иногда переход должен быть произведён на небольшое расстояние (как в нашем приме- ре). Тогда нет смысла расходовать лишний байт на полный адрес, если вполне доста- точно диапазона +128...-127. Кроме того, команды условного перехода могут быть по- лезны и при написании так называемых рело- цируемых процедур, для которых адрес раз- мещения в памяти не имеет никакого значе- ния (до этих процедур мы ещё доберёмся - всему своё время). К сему добавлю маленький вопрос: а что будет, если в нашем примере вместо "2" поставить "-2"? И напоследок, как водится, о влиянии ко- манд "JR" и "JP" на флаги. Так вот - все эти команды абслютно никакого влияния на флаги не оказывают! Организация цикла Рассмотрим случай, когда нам необходимо организовать циклическое выполнение неко- торого участка программы. Пусть в регистре "B" хранится количество повторов. Итак... LD B,5 LOOP ........ тело цикла ........ DEC B JR NZ,LOOP Приведённый фрагмент может поначалу выз- вать некоторое недоумение: "А что такое LOOP?" Ответ прост. Дело в том, что когда Вы пишете программу в каком-либо Ассембле- ре, то он предоставляет для удобства такое понятие, как "метка". Все ссылки аресуются на неё, а не на реальный адрес, что весьма облегчает написание и отладку программы. Ведь слово запомнить гораздо проще, чем, например, "#7A3E". Да и при необходимости добавить строку-другую в уже готовую прог- рамму вам не придётся самому пересчитывать все адреса - это произойдёт автоматически. Должен заметить, что такого фрагмента программы, как этот, вы почти нигде не встретите. Сочетание команд DEC B: JR NZ,LOOP почти всегда заменяют одной командой DJNZ, читающейся, как "уменьшить В и перейти, если не ноль". Эта команда - двухбайтная, и поэтому её использование даёт некоторую экономию памяти. Опять-таки, из-за неё ре- гистр В обычно используют для организации счётчиков. Имеет она и недостаток: считать можно только до 256, но это можно обойти, использовав вложенность. А теперь перепишем нашу программу с учётом всего вышеизоженного: LD B,5 LOOP ........ тело цикла ........ DJNZ LOOP Продолжение следует... Из газеты Optron #29, Львов, 21.07.99 Ассемблер: взгляд издалека. Продолжение. Начало в NN 20, 21, 24, 25, 28 (C) Инфарх, 1999 Что такое стек? Наверняка вы уже слышали это слово. По- пробуем выяснить, что скрывается за ним. "Стеком" называют память, при работе с которой используется принцип, именуемый "first in, last out (первым вошёл, послед- ним вышел)". Это означает, что данные (слова) заносятся на стек последовательно, одно за другим, и считываются с него в об- ратном порядке. При этом доступ возможен только к так называемой "вершине стека", т.е. к последнему помещённому туда слову. Стек предназначен для хранения процессо- ром временных данных, таких, в частности, как адреса возврата из подпрограмм. В процессоре Z80 за размещением информа- ции в стеке следит регистр SP, в котором сохраняется адрес вершины стека. Отметим один важный момент: при занесении очеред- ного слова в стек адрес его вершины смеща- ется вниз (к младшим адресам), т.е. не увеличивается, а уменьшается. Таким обра- зом, если в SP у вас записан адрес #6000, то следующее слово, помещённое на стек, разместится в памяти по адресу не #6002, a #5FFE. Так стек адресуется в большинстве микропроцессорных систем (но, конечно, нет правил без исключений...) Определение области стека Конечно, с областью, отведённой под стек, можно работать, как и с любой другой областью памяти. Но, используя специальные команды процессора, предназначенные для работы со стеком, можно значительно уско- рить и упростить работу программы. Как же использовать стек? Для начала надо позаботиться о записи в SP адреса вершины стека. Команды здесь выглядят так: LD SP,(addr) LD SP,IX LD SP,IY LD SP,HL LD SP,nn Влияния на флаги это не оказывает. А после одного из вышеприведённых действий в SP окажется желаемое значение. Но если вам этого недостаточно, то можно использовать следующие действия: INC SP DEC SP Тем самым вы уж точно сможете придать SP требуемое значение. А если говорить о вли- янии на флаги, то ничего необычного здесь тоже не происходит. Всё в пределах того типа команд, которые используются в опера- ции. Работа со стеком В самом простом варианте на стеке можно размещать информацию и, естественно, полу- чать её оттуда. Итак, размещение. Для этого мы использу- ем команду "PUSH" в следующих вариантах: PUSH rr PUSH ii Как и принято у нас, "rr" обозначает ре- гистровую пару, а "ii" - индексный регистр IX или IY. Когда процессор столкнётся с суровой необходимостью выполнить такую ко- манду, то первым делом уменьшит содержа- щийся в SP адрес на два. После этого по получившемуся адресу будет переслано со- держимое регистровой пары, указанной после "PUSH". Таким методом мы можем сохранять на стеке данные. А как их оттуда снимать? Тоже очень просто. Команда, обратная "PUSH", записы- вается как "POP". Запись её полностью ана- логична вышеописанной: POP rr POP ii Выполняя эту команду, процессор возьмёт слово, адрес которого хранится в SP, и по- местит его в регистровую пару, указанную в команде, после чего содержимое SP будет увеличено на два. На будущее давайте дого- воримся: если вам встретится фраза типа "поместить на стек" или "снять со стека", то подразумевается и естественная коррек- ция содержимого SP. И ещё один совет: при работе со стеком не забывайте, в какой последовательности вы помещали в него дан- ные, иначе ваша программа рискует уподо- биться Windows'95! А вот ещё несколько команд для работы со стеком: EX (SP),HL EX (SP),IX EX (SP),IY По ним происходит обмен между содежимым вершины стека и содержимым HL или ин- дексного регистра; ADC HL,SP ADD HL,SP ADD IX,SP ADD IY,SP Применяя их, значение SP можно использо- вать в некоторых видах вычислений. А теперь - о флагах. Как "PUSH", "POP", так и "EX" никакого влияния на флаги не оказывают, но... если вы выполните команду "POP AF", то, естественно, флаги будут уже далеко не те, так что не забывайте об этом! А что касается арифметических действий, то там воздействие на флаги тоже вполне обычное и соответствует прочим арифметическим операциям. Подпрограммы Для начала определимся с терминологией. "Подпрограммой" называют фрамент основной программы, который может быть вызван из любого места оной для выполнения опре- делённой задачи, после отработки которой управление возвращается основной программе с того места, откуда была вызвана подпрог- рамма. Для её вызова используется команда "CALL", запись которой может принимать следующие формы: CALL addr CALL cc,addr Надеюсь, вы не забыли, что "cc" обозна- чает возможность использования условия. А условия допускаются такие: C, M, NC, NZ, P, PE, PO, Z Как водится, выполняется команда "CALL" не просто, а очень просто. Встретив её, процессор поместит на стек адрес команды, следующей за "CALL". После этого управле- ние будет передано тому фрагмету програм- мы, который расположен по адресу "addr". Вот так и произойдёт вызов подпрограммы. А как вернуться из неё? И того проще! Подпрограмма должна заканчиваться командой "RET", встретив которую, процессор снимет с вершины стека слово для использования его в качестве адреса перехода. Если вы ничего со стеком не начудили, этим словом будет адрес, помещённый туда командой "CALL". Теперь вам понятно упоминание в одном из первых наших занятий команды "RET"? Дело в том, что когда вы запускаете програму из Ассемблера, он выполняет её как подпро- грамму, и, что естественно, рассчитывает на корректный выход. И напоследок - о вариантах написания ко- манды "RET": RET RET cc Вот и все сложности! Даже адрес не надо указывать. Условия здесь тоже возможны. Они полностью аналогичны тем, что ис- пользует команда "CALL". Неужели всё??? А вот и нет! Осталась ещё одна команда, вполне подходящая к теме сегодняшнего за- нятия. Это так называемый "вызов рестар- та". Рестарт - это почти то же самое, что и "CALL", но он позволяет совершать вызов только восьми процедур, расположенных в первых 256 байтах памяти: #00, #08, #10, #18, #20, #28, #30, #38 Преимуществом команды рестарта (RST) яв- ляется то, что она занимает всего один байт и срабатывает быстрее, чем "CALL". А недостатком - то, что применить её можно только для обращения к одной из восьми приведённых выше ячеек. В Спектруме все эти адреса относятся к ПЗУ, но если вы знаете, что они делают, то можете ис- пользовать их в своих программах. Варианты записи команд рестарта следую- щие: RST #00 RST #08 RST #10 RST #18 RST #20 RST #28 RST #30 RST #38 Небольшое примечание Итак, со стеком мы разобрались. Добавлю ещё пару слов. Электронная газе- та "Полесье" тоже приступила к публикации заметок типа нашего "Ликбеза". Так что, если заинтересуетесь - обращайтесь и к этому изданию за дополнительной информаци- ей. Вот и всё на сегодня. Не бойтесь экспе- риментировать, ведь "на глюках учатся"! До новых встреч! Продолжение следует... Из газеты Optron #30, Львов, 04.09.99 Ассемблер - взгляд издалека Продолжение. Начало в NN 20, 21, 24, 25, 28, 29 (C) Инфарх, 1999 Команды сдвига Что такое сдвиг? Смысл сдвига в том, что все биты в бай- те, не меняя своего положения относительно друг друга, смещаются вправо или влево. А в завсимости от типа команды биты, "уходя- щие за край" байта, могут появиться с про- тивоположной стороны (циклические сдвиги) или затеряться в первозданном хаосе (сдви- ги простые или нециклические). Пример. Применим к числу %01100010 циклический сдвиг вправо. Результатом будет число %00110001 А используя простой сдвиг влево, получим %1100010? Знак "?" означает, что бит принял состо- яние, нам заранее неизвестное, и зависящее от типа команды, состояния флагов и т.д. Команды сдвига аккумулятора Существуют четыре команды смещения битов в аккумуляторе: RLCA RLA RRCA RRA Вторая буква здесь красноречиво свиде- тельствует о направлении сдвига. Команды "RLCA" и "RLA" сдвигают содержимое "А" влево, "RRCA" и "RRA" - вправо. А если в мнемонике команды есть буква "C", то это указывает на участие в операции флага пе- реноса CY. Для пущей наглядности проиллюстрируем исполнение таких команд. +--+ +-+-+-+-+-+-+-+-+ |CY|<-++7|6|5|4|3|2|1|0|<-+ +--+ |+-+-+-+-+-+-+-+-+ | +-------------------- Как видно из привeдённой схемы, при вы- полнении "RLCA" происходит циклический сдвиг битов аккумулятора влево. Бит, зани- мавший крайнее левое положение (D7), пере- ходит на место крайне правого (D0) и ко- пируется в флаг переноса "CY", а прочие биты просто смещаются влево. Это можно с успехом применять для проверки содержимого крайнего бита: LD A,%10011010 RLCA JR C,MET ... MET Приведённый фрагмент программы вполне пригоден к эксплуатации. Естественно, за- грузка "A" будет не такой явной, иначе ка- кой смысл в проверке? Но суть в том, что если в старшем бите аккумулятора будет "1", то процессор совершит переход на мет- ку "MET", тем самым отказав в исполнении фрагменту програмы, обозначенному в нашем примере многоточием. А теперь - команда "RLA": +--+ +-+-+-+-+-+-+-+-+ +-+CY|<--+7|6|5|4|3|2|1|0|<-+ | +--+ +-+-+-+-+-+-+-+-+ | +---------------------------+ Перед вами циклический свиг аккумулятора вправо. В отличие от "RLCA" здесь флаг пе- реноса тоже принимает участие в работе, предоставляя своё содержимое в качестве ещё одного бита для сдвига. Пример. Если в "А" находится число %01101010, и флаг переноса установлен, то после ис- полнения "RLA" в "А" будет находится число %11010101, и флаг "CY" окажется сброшенным. А вот сдвиги в противоположную сторону: +-+-+-+-+-+-+-+-+ +--+ +->|7|6|5|4|3|2|1|0+-+>|CY| | +-+-+-+-+-+-+-+-+ | +--+ +--------------------+ +-+-+-+-+-+-+-+-+ +--+ +->|7|6|5|4|3|2|1|0+-->|CY+-+ | +-+-+-+-+-+-+-+-+ +--+ | +---------------------------+ Как видите, команды "RRCA" и "RRA" пол- ностью идентичны рассмотренным ранее, за исключением направления сдвига. Команды сдвига регистров общего назначения Выше мы рассмотрели действия с аккумуля- тором. Но и все прочие регистры также "не остались обездоленными" в этом плане. Даже наоборот, команд для них ещё больше. Есть, например, группа команд: RLC s RL s RRC s RR s В них "s" обозначает "(HL)", "(IX+d)", "(IY+d)" или один из регистров: "A", "B", "C", "D", "E", "H", "L" Во всём остальном эти комады аналогичны тем, что относятся к "A": "RLC s", "RL s" выполняют сдвиг "s" влево, "RRC s", "RR s" - вправо. Флаг переноса участвует в опера- ции аналогичным образом. Рассмотрим теперь "SLA s" и "SRL s": +--+ +-+-+-+-+-+-+-+-+ |CY|<--+7|6|5|4|3|2|1|0|<--0 +--+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +--+ 0-->|7|6|5|4|3|2|1|0+-->|CY| +-+-+-+-+-+-+-+-+ +--+ Эти команды весьма широко распростране- ны, т.к. помимо сдвига влево или вправо они заполняют освобождающееся место нуле- вым значеием, следовательно, могут рассматриваться как умножение и деление на два соответственно. И, наконец, команда "SRA s": +----+ | +-+-+-+-+-+-+-+-+ +--+ +-->|7|6|5|4|3|2|1|0+-->|CY| +-+-+-+-+-+-+-+-+ +--+ Её можно рассматривать как операцию "де- ление на два со знаком". В сдвиге принима- ют участие только 7 младших бит, а старший остаётся без изменения. Но не подумайте, что это всё! Существует ещё... Сдвиг полубайтов Он выполняется по "RLD" и "RRD". Эти ко- манды используются не так уж и часто. Быть может, вам вообще не придётся обращаться к ним. Но, тем не менее, рассмотреть их всё же надо. Итак, схема: +---->----------+ +-----+--+--+ +-----+--+--+ |7...4|3...0| |7...4|3...0| +-----+--+--+ +-+-+-+--+--+ A +----<---+ +-<--+ (HL) +----<----------+ +-----+--+--+ +-----+--+--+ |7...4|3...0| |7...4|3...0| +-----+--+--+ +-+-+-+--+--+ A +---->---+ +-->-+ (HL) Как видите, в работе принимают участие одновременно как "A", так и "(HL)". Обмен между ними происходит группами по 4 бита, причём старшие 4 бита аккумулятора остают- ся без измнений. Пример. Если в аккумуляторе содержится байт %11010010, а по адресу "HL" хранится байт %01111001, то после исполнения команды "RLD" в акку- муляторе окажется число %11010111, а "HL" будет адресовать байт %10010010 Команда "RRD" выполняется аналогично, за исключением, конечно, направления сдвига, как и видно на схеме. Ну и, конечно, рассмотрим такую важную часть любой команды ассемблера, как... Влияние команд сдвига на флаги +-----------+---------------+ | Мнемоника | Флаги | | операции +---------------+ | | C Z P/V S N H | +-----------+---------------+ | RL s | x x P x 0 0 | | RR s | x x P x 0 0 | | RLC s | x x P x 0 0 | | RRC s | x x P x 0 0 | | RLA | x . . . 0 0 | | RRA | x . . . 0 0 | | RLCA | x . . . 0 0 | | RRCA | x . . . 0 0 | | RLD | . x P x 0 0 | | RRD | . x P x 0 0 | +-----------+---------------+ Вот и всё влияние! Все обозначения в таблице должны быть вам знакомы из прошлых занятий. Единствен- ный новый знак - "s", но о нём было сказа- но выше. Продолжение следует... Из газеты Optron #32, Львов, 23.10.99 Ассемблер - взгляд издалека Продолжение. Начало в NN 20, 21, 24, 25, 28-30 (C) Инфарх, 1999 Работа с внешними устройствами К таковым относятся клавиатура, магнито- фон, принтер, много ещё чего (зависит от конкретного варианта). Так как же процес- сор обменивается с ними информацией? Оче- видно, что они не включены непосредственно в шину данных (ШД). Между внешним устройством (ВУ) и процессором есть такая вещь, как "порт". Только не подумайте, что я про морской порт вам рассказывать буду! На самом деле "порт" - это устройство, которое по коман- де процессора подключит к ШД определённое ВУ. Какое - зависит от его адреса. Напри- мер, порт kempston joystick'а имеет адрес #1F, бипер - #FE и т.д. Теоретически количество портов может быть 65536, но на практике это число го- раздо меньше и зависит от аппаратной реа- лизации вашего Speccy. Здесь имеет значе- ние метод дешифрации порта, т.е. определе- ние того, какое ВУ в данный момент должно быть подключено к ШД. К примеру, хоть порт джойстика имеет номер #1F, его дешифрация чаще всего происходит только по разряду A5 шины адреса (ША). Рассмотрим сей процесс подробнее. При- знаком работы с портом, является низкий уровень сигнала IORQ. Какой процесс проис- ходит (чтение из порта или запись в него), подскажет низкий уровень сигналов RD или WR соответственно. И если при таковом со- четании сигналов (IORQ=0; RD=0) сигнал A5 ША имеет низкий уровень, то будет выбран порт джойстика. При этом значение, прочи- танное из него, появится на ШД. Запись происходит аналогично, только используется сигнал WR и число с ШД попадает в порт. А вот как управлять всеми этими сигнала- ми без помощи паяльника и большого коли- чества пива? Для этого процессор распола- гает специальными командами, которые я вам сейчас и продемонстрирую. Чтение данных из порта Наверняка в бейсике вам встречалась ко- манда IN. Так вот, в ассемблере есть точно такая же. Она присутствует в таких вариан- тах: IN A,(n) IN r,(C) Первый вариант команды производит чтение из порта, младший байт адреса которого за- писан в скобках, а старший - в аккумулято- ре. Байт, прочитанный из порта, будет по- мещён в "A". Для того, чтобы с помощью этой команды прочитать состояние kempston joystick'а, надо записать её сле- дующим образом: IN A,(#1F) Как я уже говорил, дешифрация порта #1F происходит, как правило, только одним раз- рядом ША, поэтому содержимое регистра "A" может быть любым. Хвастайтесь перед всеми, что ваш компьютер имеет 256 портов джойстика! А теперь обратимся ко второму варианту. В качестве приёмника для считанного байта служит "r", который на практике может быть одним из регистров: A, B, C, D, E, H, L Адрес порта помещается в регистровую па- ру BC. Этот вариант наиболее удобен, если вам следует использовать полную адресацию порта. К примеру, рассмотрим чтение клавиатуры. Адреса портов клавиатуры отличаются только старшим байтом, а младший всё время равен #FE. Какой полуряд в данный момент читает- ся, видно из таблички: +-----------+-------+ | Полуряд | Адрес | | | порта | +-----------+-------+ | Space...B | 7FFE | | Enter...H | BFFE | | P.......Y | DFFE | | 0.......6 | EFFE | | 1.......5 | F7FE | | Q.......T | FBFE | | A.......G | FDFE | | CS......V | FEFE | +-----------+-------+ Одной из особенностей чтения из порта клавиатуры является то, что младшие биты полученного значения относятся к крайним клавишам полуряда. Наример, если мы выпол- ним нечто вроде LD BC,#EFFE IN A,(C) то полученный байт будет иметь следующее значение: +--+--+--+--+--+--+--+--+ |x |x |x |6 |7 |8 |9 |0 | Клавиша +--+--+--+--+--+--+--+--+ |D7|D6|D5|D4|D3|D2|D1|D0| Бит +--+--+--+--+--+--+--+--+ Битам D7, D6, D5 повезло - они имели честь остаться неиспользованными. О том, что определённая клавиша нажата, мы узнаем по состоянию соответствующего ей бита - он будет сброшен. Расширим приведённый выше пример: LD BC,#EFFE;полуряд 6...0 IN A,(C) ;чтение состояния RRCA ;проверка JR NC,ZERO ;нажат "0" ;продолжаем работу ..... ..... ZERO Вот так и просходит работа с клавиату- рой. Не правда ли, все просто? А теперь давайте рассмотрим процесс об- ратный - Команды записи в порт Они весьма похожи на команды чтения. Вот, взгляните: OUT (n),A OUT (C),r Запись в порт аналогична чтению, по крайней мере, в том, что относится к пере- даче параметров. К примеру, рассмотрим запись в порт #FE. На него возложена не одна задача, а сразу несколько: младшие три бита определяют цвет бордюра, бит D3 посылает импульс на гнёзда MIC и EAR, а бит D4 включает или выключает бипер. Итак, допустим, что вам необходимо уста- новить красный бордюр. Для этого выполните следующее: LD A,2 OUT (#FE),A Пару слов о бите D4. Всякий раз, когда вы переводите его в противоположное состо- яние (разумеется, высылая результат в порт), внутренний динамик издаёт краткий щелчёк. Таким образом, манипулируя этим битом с определённой скоростью, вы можете создавать простые звуки. Так, на сегодня вроде бы всё... Что? Ах, да, я совсем забыл о влиянии на флаги! Ну, давайте посмотрим, что у нас тут... +-----------+---------------+ | Мнемоника | Флаги | | операции +---------------+ | | C Z P/V S N H | +-----------+---------------+ | OUT (n),A | . . . . . . | | OUT (C),r | . . . . . . | | | | | IN A,(n) | . . . . . . | | IN r,(C) | . x P x 0 x | +-----------+---------------+ Как видите, и здесь всё довольно просто. Ну, теперь действительно всё. Итак, до следующего раза! Продолжение следует... Из газеты Optron #33, Львов, 08.12.99 Ассемблер - взгляд издалека Продолжение. Начало в NN 20, 21, 24, 25, 28-30, 32 (C) Инфарх, 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 ? | +-----------+---------------+ А вот теперь действительно всё... на се- годня. А вообще-то нам с вами ещё долго предстоит "Учиться, учиться, и учиться". Но это потом, а пока - до свидания! Продолжение следует... Из газеты Optron #35, Львов. 26.04.2000 Ассемблер - взгляд издалека Продолжение. Начало в NN 20, 21, 24, 25, 28-30, 32, 33 (C) Инфарх, 2000 Привет, энтузиасты! Что, небось заскуча- ли на каникулах? Ничего, сейчас продолжим. На этот раз мы с вами поговорим о работе с прерываниями. Для начала уясним, что это такое. Итак... Прерывания Вы, может, и не подозреваете, что 50 раз в секунду процессор отвлекается от обра- ботки вашей программы и занимается неиз- вестно чем... Например, опрашивает клавиа- туру, изменяет значение системной перемен- ной FRAMES и т.д. Причём, делает это так, что вы об этом и не догадываетесь. В эти моменты происходит следующее: процессор обращается к подпрограме по адресу 56, как будто выполняет команду RST 56 или CALL 56. Отличие в том, что переход осу- ществляется не программно, а аппаратно. Завершив выполнение вызванной процедуры, процессор продолжает выполнять основную программу. На первый взгляд может показаться, что от такой "фишки" проку никакого, ведь программно в ПЗУ ничего не изменишь. К счастью, программист имеет возможность по- менять адрес, по которому расположена про- цедура обработки прерываний. Для этого ис- пользуется упомянутый ранее регистр I. Прежде всего, надо запомнить, что имеет- ся три режима обработки прерываний. Они обозначаются соответственно цифрами от 0 до 2. Стандартный режим - 1. Именно его эксплуатирует операционная систама. Нуле- вой режим нам не особенно интересен: на практике он ничем не отличается от перво- го. Заметьте, именно на практике! Разли- чия, конечно, есть, да только в Спектруме они не реализованы. Таким образом, самым интересным для нас является режим 2. Для начала пару слов о том, как он рабо- тает и что в этот момент в компьютере тво- рится. Когда приходит сигнал прерывания, процессор, для начала, определяет адрес указателя на процедуру обработки прерыва- ний. Оный состоит из двух байт: младшего, считанного с шины данных (его также имену- ют "вектор прерываний"), и старшего, счи- танного с регистра I. Полученное значение помещается на шину адреса, а предыдущее её содержимое сохраняется на стеке. Всё это, в сущности, является вариантом вызова подпрограммы. "Но что означает этот термин - "вектор прерываний"?" - спросите вы. Дело в том, что термин этот связан с работой компьюте- ров, в которых предусмотрена возможность поступления нескольких запросов на преры- вание ОДНОВРЕМЕННО. Естественно, в этой ситуации необходимо определиться, какое из прерываний имеет приоритет. А это значит, что запрос на прерывание следует рассмат- ривать как "вектор", который, как и всякий порядочный вектор, характеризется, как ми- нимум, двумя значениями (в данной ситуации - источником и приоритетом). При поступле- нии запроса на прерывание оно обрабатыва- ется или игнорируется в зависимости от приоритета над выполняемым в данный момент прерыванием. Ну, а для Спектрума, хоть в нём и ис- пользуется только одно прерывание, термин этот, тем не менее, сохранён. Вот и вся недолга. Как правило, значение вектора прерываний у Speccy равно 255, поэтому для определе- ния адреса указателя достаточно содержимое регистра I умножить на 256 и прибавить 255. Итак, попробуем вообразить те дейс- твия, которые необходимо произвести для применения собственной процедуры обработки прерывания: 1) Запретить прерывания. Это необходимо во избежание запуска новой процедуры пре- рывания во время работы текущей, что чре- вато... 2) Записать по адресу, рассчитанному за- ранее, указатель на процедуру обработки прерываний, а именно её адрес. 3) Задать в регистре I старший байт ад- реса указателя на процедуру обработки. 4) Установить второй режим обработки прерываний. 5) Вновь разрешить прерывания. Естественно, процедура обработки преры- ваний на данный момент уже должна нахо- диться в памяти. Вот вкратце и всё. Теперь - пару слов о средствах реализации. Точнее, о командах. Итак: DI запрещение прерываний EI разрешение прерываний IM 0 IM 1 IM 2 Установка одного из трёх режимов прерываний Теперь у вас есть и команды. Однако пе- ред установкой своей процедуры обработки подумайте о том, должна ли она отключаться и возвращать управление стандартной проце- дуре. Для отключения же необходимо: 1) Запретить прерывания. 2) Восстановить значение I, записав в него 63. 3) Назначить командой IM 1 первый режим прерываний. 4) Разрешить прерывания. Но и это ещё не всё. Необходимо помнить, что некоторые внешние устройства могут из- менять значение вектора прерываний. Кроме того, если ваш Speccy собран на коленях "кривыми ручками" по заляпанной пивом схе- ме, вектор прерывания может скакать самым непредсказуемым образом. Естественно, в таком случае вероятность вызова процедуры обработки равняется 1/256. Во избежание глюков и головной боли программисты прибе- гают к следующему приёму: вместо записи двух байт по определённому адресу выстраи- вается таблица размером не менее 257 байт с таким расчётом, чтобы в любом случае считывался один адрес. Естественно, для этого все элементы таблицы должны быть одинаковы. Наиболее удачным значением для заполне- ния этой таблицы является 255. Не пугай- тесь, что адрес процедуры будет равен #FFFF. Этого одного байта как раз хватит для размещения кода 24 (команда JR). За ней последует адрес #0000, по которому в ПЗУ находится значение #F3. Таким образом получается JR 65524. По этому адресу уже можно разместить комаду JP с самым произ- вольным адресом перехода. Рассмотрим пример установки своей про- цедуры обработки прерываний: LD A,24 ;код команды JR LD (65535),A LD A,195 ;код команды JP LD (65524),A LD HL,ISR ;адрес обработчика LD (65525),HL LD HL,#FE00 LD DE,#FE01 LD BC,#0100 LD (HL),#FF LD A,H LDIR DI LD I,A IM 2 EI Вот мы и подключили процедуру обработки прерываний. Рассмотрим теперь обратный процесс, а именно возврат к режиму IM 1. DI LD A,63 LD I,A IM 1 EI И напоследок - пример. Допустим, вы хо- тите воспроизводить музыку во время работы вашей программы. Если вы используете мело- дию, откомпилированную в одном из музы- кальных редакторов и оснащённую стан- дартным плеером, проблем у вас не возник- нет. Для проигрывания необходимо каждые 1/50 секудны вызывать плеер, а значит, частота прерываний вам идеально подходит. Перед тем, как начать воспроизведение, не- обходимо проинициализировать плеер, сделав CALL по сответствующему адресу. Адрес ини- циализации, равно как и проигрывания, за- висит от конкретного редактора и условий компиляции. Однако часто бывает, что ини- циализация расположена в самом начале ме- лодии, а проигрывание ноты ещё на шесть байт дальше. Если допустить, что мелодия компилирована под адрес #C000, то инициа- лизация будет расположена по адресу #C000, а воспроизведение ноты - по адресу #C006. Теперь подкорректируем наши примеры. В процедуру установки второго режима преры- ваний добавьте CALL #C000 для инициализа- ции. Это следует проделать и с процедурой восстановления прежнего режима прерываний. Для чего? Чтобы после того, как вы отклю- чили музыку, AY не продолжал тянуть пос- леднюю ноту. Инициализация позволит вам быстро "заткнуть глотку" динамику. А теперь посмотрм на саму процедуру: ISR DI PUSH AF ;сохраняем все регистры PUSH BC ;для нормального возврата PUSH DE ;в основную программу PUSH HL CALL #C006;вызываем плеер POP HL ;восстанавливаем POP DE ;сохраненные значения POP BC POP AF EI ;разрешение прерываний RET ;и возврат Если вы всё сделали правильно и опреде- лили адреса инициализации и проигрывания, музыка должна зазвучать. Упомянём ещё одну команду для облегчения жизни - HALT. Когда процессор набредает на неё, он приостанавливает работу и ждёт сигнала прерывания. После его поступления управление передаётся процедуре обработки прерывания. А после её завершения основная программа продолжает свою работу с коман- ды, следующей за HALT. Это позволяет ис- пользовать HALT для синхронизации вашей программы с прерываниями. Ну, вроде всё пока с теорией. Осталось только, как водится, изложить кое-что о флагах и вариантах команд. Взгляните: +-----------+---------------+ | Мнемоника | Флаги | | операции +---------------+ | | C Z P/V S N H | +-----------+---------------+ | DI | . . . . . . | | EI | . . . . . . | | IM 0 | . . . . . . | | IM 1 | . . . . . . | | IM 2 | . . . . . . | | LD A,I | . x x x 0 0 | | LD I,A | . . . . . . | | HALT | . . . . . . | +-----------+---------------+ Вот и все флаги. Теперь можете смело приступать к озвучиванию своих программ. Не забывайте только, что некоторые плееры имеют привычку разрешать прерывания. Также непосредственно после перехода на процеду- ру обработки прерываня по HALT прерывания оказываются запрещёнными. Продолжение следует... Из газеты Optron #36, Львов, 17.06.2000 Ассемблер - взгляд издалека Продолжение. Начало в NN 20, 21, 24, 25, 28-30, 32, 33, 35 (C) Инфарх, 2000 Друзья мои, я опечален! Все имеющиеся в языке Ассемблер команды нами уже изуче- ны... Но не падайте духом! От изучения средств языка мы переходим непосредственно к прог- раммированию. Для того, чтобы сей процесс увенчался успехом, необходимо для начала как следует усвоить... Распределение памяти Speccy Итак, память... С адреса #0000 и по ад- рес #3fff включительно расположено ПЗУ ва- шего компьютера. Там прошита операционная система (ОС). Естественно, в её составе найдётся немало полезных процедур, которые попытаются вас соблазнить и заставить ис- пользовать себя в ваших программах. Конеч- но, это вполне допустимо. Но не всегда... ОС - программа высоконадёжная, универ- сальная. Но за эти плюсы мы вынуждены расплачиваться тормозами в работе и слож- ностями при использовании вышеупомянутого ПЗУ. Лично меня такое состояние дел не устраивает, и потому все нужные средства я делаю, по возможности, сам. Но это лирика, продолжим урок. Следом за ПЗУ расположена область памяти, отведённая под экран. Она начинается с адреса #4000 и заканчивается адресом #5aff. Причём, до адреса #5900 находится область графики, а далее - атрибуты. Затем идут области памяти, занятые пере- менными ОС. Размеры этих областей меняются в зависимости от разных факторов, однако, как правило, можно раcсчитывать на то, что с адреса #6000 по адрес #ffff - свободное пространство. Таким образом, если вы решили работать полностью независимо от ОС, то можете сме- ло использовать всю оперативную память. Однако для того, чтобы иметь возможность иногда обратиться к ОС, желательно ниже #6000 не уходить. Не забывайте и о том, что некоторое количество памяти необходимо отвести под стек. Мне, как правило, наибо- лее подходил вариант, при котором значение регистра SP в самом начале программы уста- навливалось в #6000. Это давало возмож- ность использовать те байты, которые ещё оставались между моей программой и систем- ными областями. Кстати, обычно, когда вы находитесь в оболочке редактора и запускаете программу из-под него, стек будет установлен так, что проблем в работе у вас, как правило, не возникнет. Но хватит! Пора уже попробовать написать некую программу. Загрузите ваш редактор. Заодно вспомните, что разные редакторы имеют некоторые отличия. Когда я буду при- водить примеры, имейте в виду, что подра- зумевается редактор XAS. Если же вы поль- зуетесь другим, вносите коррективы соглас- но его специфике. Итак, начало программы. Как правило, это оператор ORG, который указывает редактору, с какого адреса вы хотите разместить код, полученный после компиляции вашего текста. Поэтому смело ставьте первой строкой вашей программы ORG #6000. Теперь разберёмся, какими ресурсами вы располагаете, если у вас - Spectrum 128. Конечно, он имеет те же области памяти, что и 48-ой вариант. Но помимо их вы може- те использовать и т.н. "страницы памяти". Для более успешного понимания этого взгля- ните на схему: +-----------------+ | страница 7 | +-----------------+ | | страница 6 |-+ +-----------------+ | | страница 4 |-+ +-----------------+ | | страница 3 |-+ +-----------------+ | | страница 1 |-+ +=================+ | | |-+ | страница 0 | | | |=================| #C000 | | | страница 2 | | | |-----------------| #8000 | | | страница 5 | | | |-----------------| #4000 | |-+ | ПЗУ 128 (16k) | | | | | +------------------ | #0000 | ПЗУ 48 (16K) | +-----------------+ #0000 Перед вами - модель страничной памяти. Цифры справа показывают адрес начала об- ластей памяти, которые мы и рассмотрим по порядку. В самом начале расположено ПЗУ. Далее - экранная область, системные пе- ременные и всё прочее, что уместится до адреса #8000. Этой странице памяти присво- ен N5. Затем - страница N2 (#8000 - #BFFF). Вышеназванные страницы всегда находятся на своём месте и потому никаких проблем с ними нет. Но вот за ними начинается самое интересное... Область памяти с #C000 по #FFFF предназ- начена для отображения тех страниц, кото- рые являются дополнительными. При рестарте там отображается страница N0, вместо кото- рой можно подключить страницы NN 1, 3, 4, 6 или 7. Специфика такова, что может быть подключена только одна страница, и, пока она активна, все остальные пребывают "в тени". Можно провести аналогию с альтерна- тивным набором регистров (команда EXX, помните?). Только там было два набора ре- гистров, а здесь вы видите шесть страниц. Для их переключения служит порт #7FFD, ку- да посылается байт управления. Перед тем, как дать подробное разъясне- ние каждому биту этого порта, немного об- щей информации насчёт экрана. В 48-ом ва- рианте область экранной памяти расположена начиная с адреса #4000 (страница 5). Для 128-го Спектрума ситуация аналогична, но... Область памяти в #1B00 байт, расположен- ная в странице N7, начиная с адреса #С000, может быть использована как дополнительный экран. Его организация будет в точности соответствовать обычному экрану во всём, кроме, разумеется, адреса размещения. А сейчас - опишем порт: Биты D0-D2 определяют, какая страница будет отображена с адреса #С000. Бит D3 заведует активным экраном. Если он установлен в "0", активен стандартный экран (в странице 5). Бит D4 управляет текущим ПЗУ. Он, будучи установленным в "0", подключает ПЗУ-48, а при установке его в "1" - ПЗУ-128. Бит D5 блокирует порт. Если заслать в него "1", порт отключается и до следующего Reset'а совершенно недоступен. Биты D6,D7 проблем не вызывают: они в 128-ом варианте не задействованы. И, наконец, нечто программоподобное: ORG #6000 ; Активизируем ПЗУ-48, экран N1 ; включаем страницу 0 LD BC,#7ffd LD A,#10 OUT (C),A ; и читаем первый байт LD HL,#c000 LD A,(HL) ; изменяем его и кладём на ; то же самое место, но в странице 1 CPL LD E,A LD A,#11 OUT (C),A LD (HL),E ; восстанавливаем страницу 0 LD A,#10 OUT (C),A ; её первый байт сравниваем ; с изменённым ранее LD A,(HL) CP E ; анализируем результат JR NZ,mode128 ; mode 48 ... ... mode128 Таким образом у нас получилась програм- ма, определяющая, располагает ли компью- тер, на котором она запущена, расширенной памятью. Первый байт страницы 0 мы инвер- тируем и помещаем в страницу 1. Производим обратное переключение и сравниваем проин- вертированный байт с имеющимся. Если они равны, значит, переключения не произошло и компьютер, скорее всего, 48-ой. В против- ном случае переходим в режим работы со 128-ой памятью. Разберёмся теперь с дополнительным экра- ном. Для начала возьмите некий экранный файл и поместите его на диск с именем, скажем, "screen.C". Для того, чтобы внед- рить некий кодовый блок с диска в код программы, используем оператор "LCODE". Напишем следующее: ORG #6000 ; подключим страницу 7 LD A,#17 LD BC,#7ffd OUT (C),A ; скопируем в неё загруженный экран LD HL,scr LD DE,#c000 LD BC,#1b00 LDIR ; восстанавливаем страницу 0 ; и активизируем второй экран LD A,#18 LD BC,#7ffd OUT (C),A ; не помешает небольшая задержка ; для лицезрения результата LD BC,0 wait DEC BC LD A,B OR C JR NZ,wait RET scr LCODE "screen" Если всё прошло успешно, перед вами поя- вится картинка, которую вы поместили во второй экран. Она продержится столько, на сколько хватит цикла задержки. После этого управление перейдёт к редактору и он авто- матически восстановит экран N1. Обратите внимание и на то, что для отоб- ражения экрана N2 вовсе не обязательно иметь страницу 7 активной. Достаточно то- го, что режим работы выбран соответствую- щим битом порта. Ну, на этот раз хватит. До скорого! Продолжение следует... Из газеты Optron #41, Львов, 20.11.2000 Ассемблеp - взгляд издалека. Продолжение. Начало в NN 20, 21, 24, 25, 28-30, 32, 33, 35, 36 (C) Инфарх, 2000 Вот мы и снова встретились! После рас- сказа о распределении памяти Speccy вы на- верняка полны желания использовать полу- ченную информацию по назначению, написав какую-нибудь программу; в этом я вам пос- тараюсь помочь. Не секрет, что каждый программист желает видеть, как работает его программа. Для этого он должен организовать в своей прог- рамме интерфейс общения с пользователем. Начаать надо со средств вывода, а именно - с экрана. Итак... Общая организация экрана Не исключено, что по Бейсику вы уже зна- комы с делением экрана на строки, знако- места и т.д. Но если вы всё-таки этого не знаете, то прочитайте этот раздел внима- тельно. Минимальной единицей экрана является од- на его точка - пиксел (от англ. "picture element", pixel - элемент изображения). Собранные в матрицу 8х8, точки образуют знакоместо. В нём размещается стандартный символ. Для знакоместа также определяется атрибут - цвет пиксела установленного и цвет пиксела, сброшенного в знакоместе. Экран содержит 24 строки по 32 знакомес- та в каждой. Если необходимо указать коор- динаты определённого знакоместа, то отсчёт ведётся от верхнего левого угла экрана. Расположеное там знакоместо имеет коорди- наты 0, 0. Знакоместо справа от него имеет координаты 1, 0, а снизу - 0, 1. Знакомес- то в нижнем правом углу имеет кординаты 31, 23. Перейдём к графике. На более низком уровне экран делится на столбцы (верти- кальные) и линии (горизонтальные). Всего экран содержит 192x256 пиксел. Пиксел сле- ва вверху имеет координаты 0, 0, а справа внизу - 255, 191. Младшие три бита коорди- наты Х определяют, какой бит в байте адре- са соответствует данному пикселу. А теперь посмотрим, как всё это выводит- ся на экран. В Спектруме его экрану соответствует об- ласть памяти, начинающаяся с адреса #4000. В её начале определяется графика, а затем - атрибуты. Каждый байт в области графики определяет состояние восьми пикселов экра- на. Пиксел будет изображён на экране, если в байте будет установлен соответствующий бит. Например, возьмём байт %10011101. Пе- решлём его по адресу #4000 и посмотрим на экран. Видите, в самой верхней линии у ле- вого края экрана появились точки? Они име- ют вот такой вид: - - - - - - - - - - нет точки - - есть точка Легко понять соответствие битов и точек, сравнив пересланый в экран бит и получен- ное изображение: каждый байт в экранной области памяти соответствует 8-ми точкам и равен ширине знакоместа. Зная, что экран состоит из 32 х 24 = 768 знакомест, причём каждое знакоместо имеет по вертика- ли 8 линий (8 байт), нетрудно получить размер области графики в байтах: 32 х 24 х 8 = 6144 (#1800) байт Теперь - об атрибутах. Их область памяти расположена сразу за графической областью, начиная с адреса #5800. Т.к. атрибут зада- ётся для целого знакоместа (а знакомест у нас - 768) и каждый атрибут занимает один байт, то нетрудно вывестии и размер облас- ти атрибутов - 768 байт. Вот как этот байт определяет состояние экрана: - три младших бита (D2, D1, D0) задают цвет чернил (INK, установленныe пикселы); - биты D5, D4, D3 - цвет бумаги (PAPER, пикселы сброшенные); - бит D6 определяет яркость (BRIGHT); если он установлен, то информация на экра- не для данного знакоместа отображается с повышеной яркостью. Остался бит D7 - бит мерцания (FLASH). Если он включен, то через каждые 16/50 до- лей секунды ULA меняет местами цвет бумаги и чернил для данного знакоместа. Рассмотим байт атрибута в общей форме: Paper +----+ | | +--+--+--+--+--+--+--+--+ |D7|D6|D5|D4|D3|D2|D1|D0| +--+--+--+--+--+--+--+--+ | | | | | | +----+ | +-Bright Ink +-Flash Цвета для экрана Спектрума имеют следую- щее соответствие кодов: 0 - чёрный 1 - синий 2 - красный 3 - малиновый 4 - зелёный 5 - циан 6 - жёлтый 7 - белый Организация экранной памяти Ознакомившись с экранной памятью Спек- трума, не подумайте, что она организована линейно! Её структура более сложная, в чём мы сейчас и убедимся. Наберите и запустите такую программу: LD HL,#4000 ; Очищаем экран LD DE,#4001 LD BC,#1800 LD (HL),L LDIR LD BC,#2FF ; И устанавливаем ; его атрибут LD (HL),7 ; ink=7, paper=0 LDIR LD HL,#4000 ; Начало экранной ; памяти LD DE,#1800 ; Размер ; графической ; области SCR LD (HL),#FF ; Пересылаем ; в экран INC HL ; Следующий байт ; экрана ; Задержка 1/10 секунды для наглядности LD B,5 PAUSE HALT DJNZ PAUSE ; Повторяем для всей области графики DEC DE LD A,D OR E JR NZ,SCR RET Назначение этой, весьма простой про- граммы - последовательное заполнение об- ласти памяти графики экрана значением #FF, что соответствует одной закрашеной линии знакоместа. Кстати, если вы испoльзуете ассембер XAS, запускайте эту программу, используя комбинацию клавиш CS+R, - и вы сможете после завершения работы программы лицезреть получившееся изображение столь- ко, сколько угодно. Лишь после нажатия "any key" (не путать с RESET) вы вернётесь в редактор. Теперь рассмотрим специфику работы све- женабранной программы более подробно. Итак, первые 32 байта экранной памяти соответствуют первой линии первой строки знакомест. Впрочем, вы это видели. Навер- ное, вы подумали, что далее следует вторая линия первой строки? А вот и нет! Далее следует первая линия второй строки! И так последовательно заполняются все 8 строк - от первой до восьмой. Можно предположить, что следующий байт будет соответствовать первой линии девятой строки... Но это опять-таки неверно. Оче- редь заполнения возвращается к первой строке, на этот раз - ко второй линии. И вот так, по линии из каждой строки знако- мест, заполняются все восемь строк. Как вы уже могли заметить, экран разде- ляется на трети и переход на следующую треть происходит только после заполнения предыдущей. Конечно, такая организация эк- рана создаёт определённые сложности для программиста, но при некоторой ловкости рук это легко решается. О технических под- робностях поговорим потом, а сейчас зай- мёмся областью атрибутов. Честно говоря, особо заниматься ею и не придётся. В отличие от графики, атрибуты организованы совершенно линейно. Сначала последовательно (слева направо) заполняет- ся нулевая строка, далее очередь переходит к первой, и так до конца экрана. Дабы про- иллюстрировать всё вышесказанное, обратим- ся опять-таки к программе. В предыдущем примере удалите последнюю команду (RET) и с этой позиции введите та- кие строки: LD DE,#300 ; Размер области ; атрибутов ATTR LD (HL),2 INC HL LD B,5 PAUSE1 HALT DJNZ PAUSE1 DEC DE LD A,D OR E JR NZ,ATTR RET Вот вам наглядная демонстрация всего вы- шесказанного! Как видите, работа с атрибу- тами весьма проста. А теперь отвлечёмся на некоторое время от освоения экрана и глянем повнимательнее на ту программу, которую вы только что набрали. Естественно, она не идеальна. Ведь это только пример, в котором нагляд- ность - важнее всего. А вот давайте попро- буем записать его так, чтобы он был как можно более компактным. Тем более, что все команды ассемблера вам уже известны. Итак... LD HL,#4000 PUSH HL LD DE,#4001 LD BC,#1800 PUSH BC LD (HL),L LDIR LD BC,#2FF LD (HL),7 LDIR POP BC POP HL LD E,#FF CALL FILL LD E,2 LD BC,#300 FILL LD (HL),E INC HL LD D,5 WAIT HALT DEC D JR NZ,WAIT DEC BC LD A,B OR C JR NZ,FILL RET Проанализировав на досуге эту програмку, вы наверняка поймёте её принцип работы. А мы вернёмся к экранным операциям. Узнав методику распределения экранной памяти, следует научиться и тому, как на- ходить в экране адрес необходимого байта (пока только байта, до пикселей мы добе- рёмся позже). Вычислить адрес первого байта первого знакоместа в строке, номер которой переда- ётся через регистр А, вам позволит такая программа: LD H,A ; В "А" находится ; номер строки RRCA RRCA RRCA AND #E0 LD L,A LD A,H AND #18 OR #40 LD H,A ; На выходе в HL адрес первого байта ; заданной строки Вот и вся сложность. Не забудьте только, что если вы хотите использовать процедуру как подпрограмму, то в конце необходимо добавить команду RET. Если вы проследите за выполнением процедуры пошагово, причём, обращая внимание на содержимое аккумулято- ра в двоичной форме, то без труда поймёте принцип работы. А теперь попробуем несколько изменить пример, чтобы получить возможность расчёта адреса с учётом ещё и столбца. Если в пре- дыдущем примере мы в качестве аргумента использовали номер строки в регистре А, то здесь надо будет использовать ещё и номер столбца в регистре L. Смотрите: LD H,A RRCA RRCA RRCA AND #E0 ADD A,L ; Добавленная строка LD L,A LD A,H AND #18 OR #40 LD H,A Как видите, путём добавления всего одной команды пример был доработан до более сложного. Попробуйте теперь сделать так, чтобы расчитывался адрес любой линии экра- на (0..191). Наверняка это окажется полез- ным упражнением. Ну, поехали дальше... Очень редко бывает необходимо что-либо делать в пределах одной линии пикселей. Обычно изображение занимает по вертикали гораздо больше места. И для того, чтобы перейти на следующую линию, вовсе не обя- зательно заново высчитывать её адрес. На- пример, если вам нужна следующая линия в пределах знакоместа (при печати символа), достаточно провести инкремент старшего байта адреса. На тот случай, если переход должен быть осуществлён без привязки к знакоместу, а просто на следующую строку в пределах эк- рана, предлагаю вашему вниманию небольшую подпрограмму: DOWN_HL INC H LD A,H AND 7 RET NZ LD A,L ADD A,#20 LD L,A RET C LD A,H SUB 8 LD H,A RET На входе сия процедура получает адрес байта в экране, а на выходе выдаёт адрес байта, расположенного под ним. Заметьте, это вовсе не должен быть первый байт ли- нии. Если вы предпочитаете использовать не регистр HL, а нечто иное, то просто заме- ните соответствующие регистры в процедуре. А заодно, раз уж зашёл разговор на эту те- му, посмотрите и на процедуру, выполняющую действие прямо противоположное - рассчиты- вает адрес строки предыдущей: UP_HL DEC H LD A,H AND 7 CP 7 RET NZ LD A,L SUB #20 LD L,A RET C LD A,H ADD A,8 LD H,A RET Иногда бывает нужно пересчитать адрес в экране в адрес, соответствующий данному знакоместу в области атрибутов. Сделать это можно так: SCR_ATR LD A,H RRCA RRCA RRCA AND 3 OR #58 LD H,A RET Ну, на этот раз пока хватит. До следующего урока попробуйте позани- маться написанием программ для рисования графических примитивов - для практики это весьма полезно. Удачи! Продлжение следует...