Программирование в машинных кодах и на языке ассемблера 1993 г.

Команды обращения к ПЗУ - Точки запуска в ПЗУ. Описание встроенного калькулятора.


5.13. Команды обращения к ПЗУ

 

Разработчики процессора Z-80 предусмотрели в нем возможность легкого и быстрого вызова, наиболее часто употребляемых подпрограмм, если они размещены в нижних областях памяти. У «Спектрума» в нижних областях находится ПЗУ, и эти команды позволяют к нему обращаться без использования конструкции CALL nn. Эти команды начинаются с мнемоники RST (restart –«перезапуск»). В этой подгруппе 8 команд. Каждая из них обращается к ПЗУ и стартует процедуру, расположенную по адресу, указанному в команде.

 

Мнемоника

Код

Эквивалент

RST 0

C7

CALL 0000

RST 8

CF

CALL 0008

RST 10

D7

CALL 0010

RST 18

DF

CALL 0018

RST 20

E7

CALL 0020

RST 28

EF

CALL 0020

RST 30

F7

CALL 0030

RST 38

FF

CALL 0038

 

Некоторые из этих процедур используются интерпретатором БЕЙСИКа и при работе в машинных кодах необходимость в них практически не возникает. Другие же используются чрезвычайно активно, без них трудно обойтись.

 

5.13.1. Команда RST 0.

По этой команде происходит запуск процедуры инициализации компьютера. Эффект от действия этой команды такой же, как и от выключения и повторного включения питания. Из БЕЙСИКа этот результат достигается командой RANDOMIZE USR 0.

 

5.13.2. Команда RST 8.

Это старт процедуры обработки ошибки. В результате ее работы в системную переменную ERR NR засылается код ошибки. Машинный стек очищается.

 

5.13.3. Команда RST 10.

Это точка входа системной программы, выполняющей печать символов на экране или на принтере, но она может применяться и для печати токенов ключевых слов, цветовых элементов, а также управляющих символов, например AT, TAB.

В ПЗУ «Спектрума» имеются и более мощные средства, способные выполнить печать сразу целых строк, но они в своей работе обязательно используют команду RST 10.

Выбор, куда выполнять печать - на экран или на принтер, зависит от того, какой канал открыт в данный момент. Канал может открываться из БЕЙСИКа командой OPEN$ или из машинного кода путем вызова соответствующей процедуры из ПЗУ.

Более подробно на примерах мы рассмотрим работу команды RST 10 во второй части – «Практикум…». Весьма подробно эта команда рассмотрена вместе с соответствующими процедурами ПЗУ в нашей книге «Элементарная графика».

 

5.13.4. Команда RST 18.

Процедура, находящаяся по этому адресу, выполняет при работе интерпретатора прием очередного интерпретируемого символа, находящегося в системной переменной CHADD и проверку его на то, является ли он печатным или нет (управляющим кодом, цветовым кодом и т.п.).

 

5.13.5. Команда RST 20.

Эта процедура выполняет последовательный просмотр символов вплоть до конца БЕЙСИК-строки. Так же, как и RST 18, используется в интерпретаторе БЕЙСИКа.

 

5.13.6. Команда RST 28.

Инициализирует внутренний калькулятор компьютера. Это точка входа обширного пакета программ. Поскольку в повседневной работе программисту не обойтись без выполнения математических расчетов, а работа калькулятора «Спектрума» в машинных кодах чрезвычайно слабо освещена, мы здесь даем подробнейшие указания по работе с ним, несмотря на то, что эта информация относится скорее к ПЗУ, а не к процессору.

Итак, встроенный калькулятор «Спектрума» дает Вам возможность выполнения сложных математических вычислений без выхода из машинных кодов. Более того, можно сказать, что он позволяет Вам выполнять многое такое, что в БЕЙСИКе просто невозможно.

Очевидным недостатком процессора Z-80 является то, что его система команд не позволяет выполнять математические и арифметические операции более сложные, чем сложение и вычитание небольших целых чисел. Хотя те, кто работает со «Спектрумом» в машинных кодах, могут воспользоваться процедурой ПЗУ, размещенной по адресу 30A9 и предназначенной для перемножения двух небольших целых чисел. Вызывается она CALL A930. Первый сомножитель должен находиться в регистровой паре HL, второй - в регистровой паре DE. Результат помещается в регистровую пару HL. Во время работы этой процедуры не портится содержимое других регистров, кроме регистра A. Если при выходе из этой процедуры результат оказывается слишком большим, включается флаг переноса (флаг C регистра F).

Даже если принять во внимание наличие этой процедуры, как быть с делением, с операциями над дробями, квадратными корнями, тригонометрическими функциями и т.п.?

Фактически такая ограниченность машинных кодов связана с тем, что нормально регистры процессора предназначены для работы с малыми целыми числами от 0  до 255. Вы не можете разделить 1 на 2 потому, что нет регистра, который мог бы хранить число 0.5. Калькулятор же обходит все эти проблемы за счет того, что работает с числами, записанными не в двухбайтной, а в пятибайтной форме, что позволяет выражать не только целые, но и действительные числа.

Такая форма представления действительных и целых чисел называется интегральной. Кстати, при работе в БЕЙСИКе все числа, которые участвуют в Вашей программе, кроме номеров строк, тоже представлены в интегральной форме. Проверьте это. Введите строку 10 LET AAA=10*20, а теперь посмотрите, как она вписалась в память: FOR I=23755 TO 23781: PRINT I, PEEK I: NEXT I .

Давайте рассмотрим, как числа представляются в пятибайтной форме.

 

Положительные целые числа.

Они могут принимать значения от 0 до 65535. В принципе для их записи хватило бы и двух байтов. Если старший байт мы назовем hh, а младший - ll, то в пятибайтной форме такое число за пишется:

00 00 ll hh 00

Как видите, опять младший байт стоит впереди старшего.

 

Отрицательные числа.

Они могут принимать значения от -1 до -65535. При этом -1 выглядит как FFFF; -2 как FFFE и т.д. В пятибайтной форме они записываются:

00 FF ll hh 00

 

Действительные числа.

«Спектрум» в состоянии работать с действительными числами из диапазона от -1.7014118*1038 до +1.7014118*1038. Числа, выходящие за пределы этого диапазона, недоступны для компьютера и вызывают сообщение об ошибке:

6 Number too big (6 Число слишком велико)

В пятибайтной (интегральной) форме такие большие числа хранятся в экспоненциальном представлении. В старшем (пятом) байте хранится экспонента числа, увеличенная на 128, а в остальных четырех байтах - мантисса. Причем старший бит четвертого байта указывает на знак мантиссы. Если он включен - число отрицательное, если же выключен - положительное.

Для тех, кто не помнит, что такое экспоненциальная форма, кратко поясним.

Предположим, что Вам надо записать какое-то очень большое число. Если Вы будете его делить раз за разом на два до тех пор, пока оно не станет по абсолютной величине меньше единицы, то сколько раз Вам пришлось его делить - это и есть экспонента, а то, что осталось после последнего деления - мантисса. Обратите внимание на то, что мантисса всегда больше 0,5, но меньше 1. Мантисса хранится в четырех младших байтах, а экспонента - в пятом (старшем). Обратная операция выполняется так: мантиссу надо умножить на двойку в степени экспоненты.

Если же число было малой десятичной дробью, например 0,0000375, то делить на два его, конечно, бессмысленно. В этом случае его нужно умножать на два до тех пор, пока оно не станет больше, чем 0.5, но меньше, чем 1. Только экспонента в этом случае считается отрицательной. На знак экспоненты указывает ее величина. Если в старшем байте стоит число, большее 128, то экспонента положительная, а если меньше, то отрицательная.

«Спектрум» ограничен и в работе с очень малыми дробями. Числа от -1.469367*10-39 до +1.469367*10-39 он не воспринимает и считает их нулем.

А как же в пятибайтной форме изображается ноль? Единственная возможная и самая естественная форма: 00 00 00  00 00

Поскольку представление дробей в двоичной форме выглядит не очень очевидным, да к тому же может возникнуть путаница со знаками, мы здесь приведем пример из книги Виккерса «БЕЙСИК ZX-Спектрума» (эта книга является официальной фирменной инструкцией к компьютерам «ZX-Spectrum 16» и «ZX-Spectrum 48»).

Предположим, что Вам надо записать в интегральной форме число 1/10. Оно меньше, чем 0,5. Будем последовательно умножать его на два, а когда старший разряд будет переходить за единицу, будем сносить ее в двоичную дробь:

 

      Десятичная форма        Двоичная форма

            0.1                     0.

            0.1*2=0.2               0.0

            0.2*2=0.4               0.00

            0.4*2=0.8               0.000

                  ┌──────────> ──────────┐

            0.8*2=1.6               0.0001

                  ┌──────────> ───────────┐

            0.6*2=1.2               0.00011

            0.2*2=0.4               0.000110

            0.4*2=0.8               0.0001100

                  ┌──────────> ──────────────┐

            0.8*2=1.6               0.00011001

и так далее.

 

Таким образом, двоичная форма десятичного числа 0.1:

      0.000110011001100110011001100… и т.д.

Сместим дробную точку вправо до первой единицы. Количество шагов даст нам экспоненту, а оставшаяся часть будет мантиссой. В нашем случае экспонента равна -3, а мантисса -0.1100110011001100… .

Для хранения этого числа в компьютере оно преобразуется в интегральную форму следующим путем:

1)                             Запишем в пятом байте величину экспоненты, увеличенную на 128, т.е. в нашем примере это будет 125.

2)                             Запишем первые 8 знаков мантиссы в четвертом байте, очередные 8 - в третьем и т.д.

3)                             В старшем (седьмом) бите четвертого байта мантиссы поместим 0, если число положительное и 1, если число отрицательное.

Итак, мы имеем:

5 байт            4 байт            3 байт            2 байт      1 байт

01111001          01001100          11001100          11001100    11001100

 

Обратите внимание на то, что хоть мы и заменили значащую единицу в седьмом бите четвертого байта на 0 (чтобы показать, что число положительное), тем не менее, компьютер все же знает, что здесь должна быть единица. Это происходит потому, что мантисса всегда больше, чем 0.5, а значит этот бит, равен единице всегда, просто для положительного числа не показан.

 

Представление стрингов в пятибайтной форме.

Стринги (строки) можно тоже представить в пятибайтной форме, но поскольку они могут быть длинными, то текст должен храниться где-то отдельно. Например, Вам надо хранить в строковой переменной слово «Спектрум». В этом случае пятый байт всегда равен нулю. Четвертый и третий байты содержат младшую и старшую часть адреса, в котором хранится первый символ стринга (в нашем случае «С»). Обозначим их aa, bb. Второй и первый байт содержат длину этого стринга. Обозначим их cc, dd. Итого:

00 aa bb cc dd

 

Стек калькулятора.

Это область памяти, в которой калькулятор компьютера выполняет все операции. Он работает точно так же, как и машинный стек процессора. Разница состоит в том, что:

·         во-первых, машинный стек «растет» сверху вниз, а стек калькулятора - снизу вверх;

·         во-вторых, машинный стек оперирует с двухбайтными величинами, которые, как правило, выражают адреса, а стек калькулятора оперирует с пятибайтными числами, которые выражают действительные числа или символьные строки (стринги).

·         в-третьих, на место нахождения вершины машинного стека указывает содержимое регистра SP (указатель стека). На основание (начало) стека калькулятора указывает системная переменная STKBOT. Ее адрес - 23651, а длина - 2 байта. Есть еще одна системная переменная STKEND, которая указывает на байт ОЗУ, следующий за вершиной стека калькулятора. Ее адрес - 23653 и длина - 2 байта. Если стек пуст, то содержимое этих двух системных переменных совпадает. Самое верхнее число на стеке, таким образом, располагается в адресах от (STKEND)-5 до (STKEND)-1 [прим.6].

Применение стека позволяет следующим образом работать с калькулятором:

·         помещать данные на стек;

·         выполнять с ними математические операции;

·         снимать результат со стека.

Те, кто знаком с языком программирования ФОРТ, обнаружат, что операции с калькулятором очень похожи на работу в ФОРТ-системах.

 

Команды калькулятора.

Команды калькулятора начинаются с инструкции RST 28 (код EF) - это как бы «включение калькулятора». За этим кодом должна идти последовательность байтов, каждый из которых является командой калькулятора. Таким образом, эта последовательность байтов является последовательностью команд. Вы, конечно, знаете, что всякая последовательность команд представляет собой программу. Итак, с команды RST 28 начинается выполнение программы калькулятора. Эта программа, очевидно, написана не на БЕЙСИКе, но и не в машинных кодах. У калькулятора своя система команд, отличная от системы команд процессора Z-80. Можно считать, что эта программа написана в кодах калькулятора. Завершается эта программа командой калькулятора «end calc» (конец вычислений). Ее код - 38. Таким образом, простейшая программа калькулятора выглядит так:

 

Код

Значение

Комментарий

EF

RST 28

Включение калькулятора.

38

end calc

Выключение калькулятора.

 

Как видите, эта программа ничего не делает, но тем не менее имеет побочный эффект. Он состоит в том, что при выходе из калькулятора в регистре DE процессора находится содержимое (STKEND), а в регистре HL - (STKEND)-5. Другими словами, регистры HL и DE становятся указателями стека калькулятора. HL указывает на первый байт (байт экспоненты) числа, находящегося на вершине стека, а DE  указывает на первый свободный байт памяти ОЗУ, находящийся над стеком калькулятора.


 

Система команд калькулятора

Полная таблица системы команд калькулятора приведена в нашем «Справочнике по программированию в машинных кодах Z-80», см. третью часть нашего пособия.

Здесь мы рассмотрим основные команды и укажем на те особенности, которые должны быть учтены при программировании. Каждая команда состоит из шестнадцатеричного кода и имени, которое поясняет ее назначение. Мы будем записывать имя команд калькулятора строчными буквами, чтобы отличать их от прописных букв мнемоник Ассемблера.

Первая команда, которую мы рассмотрим - это add (сложение). Она предназначена, как следует из названия, для сложения двух чисел. Они должны находиться на вершине стека калькулятора. Работа команды состоит в том, что эти числа снимаются со стека, а на их место помещается их сумма. Таким образом, на стеке оказывается на одно число меньше, чем было. Команда add называется бинарной потому, что для ее выполнения требуются два операнда, хотя после операции получается один результат.

 

Бинарные операции.

Есть еще много различных бинарных операций и функций. Как и следовало ожидать, бинарными являются операции «subtract» (вычитание), «multiply» (умножение), «divide» (деление). Есть еще команда «power» (возведение в степень), которая возводит одно число в степень второго.

 

Унитарные операции.

Калькулятор имеет также и унитарные операции, которые используют только один операнд, снимая его с вершины стека и заменяя его результатом. Такова, например, операция «sqr» (вычисление квадратного корня). Унитарные операции оставляют длину стека калькулятора без изменений.

Рассмотрим в качестве демонстрационного примера программу для вычисления корня квадратного из суммы синуса и косинуса числа x. Перед началом программы величина x должна находиться в верхних пяти байтах стека калькулятора.

Пример достаточно прост, чтобы Вы сами смогли его проследить с помощью таблицы системы команд калькулятора (см. «Справочник…») и проникнуться духом техники выполнения вычислений и работы со стеком калькулятора.

 

Код

Команда

Число на вершине

Число перед ним

Комментарий

EF

RST 28

x

-

Включение калькулятора.

31

duplicate

x

x

Повторение числа на вершине стека.

20

cos

Cos(x)

x

Вычисление косинуса.

01

exchange

x

Cos(x)

Поменять местами вверх – два числа на стеке.

1F

sin

Sin(x)

Cos(x)

Вычисление синуса.

0F

add

Sin(x)+Cos(x)

-

Сложение.

28

sqr

-

Вычисление квадратного корня.

38

end calc

-

Выключение калькулятора.

 

В состав команд калькулятора входят также логические функции and и or. Они работают так же, как и в БЕЙСИКе.

·         x and y равно x, если y не равен нулю, в противном случае результат - 0.

·         x or y равно x, если y не равен нулю, в противном случае результат - единица.

Логическая функция not  дает единицу, если оригинал равен нулю, в противном случае - ноль.

Но здесь есть и две новые команды:

·         less_zero (Код 36) дает единицу, если оригинал меньше нуля, в противном случае дает ноль.

·         gtr_zero (Код 37) дает единицу, если оригинал больше нуля, в противном случае дает ноль.

 

К Вашим услугам пять готовых констант, которые могут быть помещены на стек немедленно. Их коды от A0 до A4 включительно.

·         stk_zero (код А0) помещает на стек число 0.

·         stk_one  (код А1) помещает на стек число 1.

·         stk_half (код A2) помещает на стек число 0.5

·         stk_pi/2 (код А3) помещает на стек одну вторую числа «пи».

·         stk_ten  (код А4) помещает на стек число 10 (десятичное)

Это очень удобно, поскольку эти числа постоянно встречаются в расчетах.

Некоторые функции калькулятора могут вызвать удивление. Например, Вы можете увидеть в таблице две функции usr. Это usr (str) и usr (num), т.е. usr (стринг) и usr (число).

В БЕЙСИКе есть только одно ключевое слово USR. Разница в том, что с его помощью в БЕЙСИКе можно выполнить две разные операции. Например, USR "J" даст Вам адрес символа графики пользователя, помещенного для использования в графическом режиме на клавишу «J». В этой команде «J» - стринг, а не число.

С другой стороны, USR_число даст Вам результат работы машиннокодовой программы, начинающейся с адреса, заданного этим числом. Попробуйте, например, PRINT 65536-USR 7962. Вы получите, сколько байтов свободной памяти у Вас осталось.

Калькулятор не настолько мудр. Он сам не в состоянии определить, является ли число на вершине стека числом или символьным стрингом. Ему надо об этом сообщить. Отсюда и необходимость в двух различных функциях usr.

Более того, команда калькулятора usr (число) одна из самых удивительных команд набора. Вот, что она делает:

·         с вершины стека снимается число и отправляется в регистровую пару BC. Разумеется, если оно помещается по размеру, а помещается туда только целое положительное число. В противном случае Вы получите сообщение об ошибке;

·         затем содержимое BC принимается за  адрес и вызывается программа, начинающаяся с этого адреса;

·         по завершении работы этой программы то значение регистра BC, которое находится там в момент выхода, помещается на вершину стека калькулятора;

·         после всего этого калькулятор переходит к исполнению следующей команды.

 

Есть и другие инструкции, различающие стринги и числа. Например, инструкция add имеет две различные команды калькулятора и, соответственно, два различных кода для работы со стрингами и числами.

Команда add (код 0F) складывает числа нормальным образом - так, что 1 + 2 = 3, в то время, как команда s_add (код 17) выполняет слияние стрингов, так что «SPEC» + «TRUM» = «SPECTRUM».

 

Интересно работает команда peek (2B). Она снимает со стека адрес, обращается по нему в память, берет то, что там содержится и помещает число на вершину стека.

 

Память калькулятора

Калькулятор «Спектрума» имеет шесть ячеек памяти. В принципе, как мы увидим позже, их количество можно увеличить. В каждой из этих ячеек можно хранить число или стринг. Набор команд калькулятора имеет команды от С0 до С5 включительно для того, чтобы помещать в эти ячейки число, находящееся на вершине стека. При этом число с вершины стека не снимается, а только выполняется его копирование в заданную пятибайтную ячейку.

Обратные команды от Е0 до Е5 могут вызывать число или стринг из памяти калькулятора и помещать его на вершину стека.

В качестве примера рассмотрим опять же вычисление , но уже другим способом, с использованием памяти калькулятора.

 

Код

Команда

Число на вершине

Число перед ним

Комментарий

EF

RST 28

x

-

Включение калькулятора.

C3

store M3

x

-

Перемещение числа в третью ячейку памяти.

20

cos

Cos(x)

-

Вычисление косинуса.

E3

recall M3

x

Cos(x)

Вызов числа из третьей ячейки памяти.

1F

sin

Sin(x)

Cos(x)

Вычисление синуса.

0F

add

Sin(x)+Cos(x)

-

Сложение.

28

sqr

-

Вычисление квадратного корня.

38

end calc

-

Выключение калькулятора.

 

Прежде, чем мы перейдем к обсуждению того, как увеличить количество ячеек памяти калькулятора, мы должны остановиться еще на одном вопросе.

Дело в том, что есть некоторые команды калькулятора и процедуры ПЗУ, которые могут нарушить содержимое ячеек памяти калькулятора. Это происходит потому, что они сами их используют при своей работе. Это надо знать, чтобы не совершить ошибку в расчетах.

·         sin, cos, tan, asn, acs, atn, ln нарушают содержимое ячеек памяти калькулятора M0, M1, M2.

·         exp - ячейки M0, M1, M2, M3.

·         int, mod_div, get_argt - M0.

·         str$ - нарушает все шесть ячеек от М0 до М5.

·         usr_n - может нарушить или не нарушить содержимое любой ячейки.

Печать графических символов (от CHR$128 до CHR$145) нарушает ячейки М0 и М1 памяти калькулятора.

Обращение к процедуре ПЗУ PRINT_FP, находящейся по адресу 2DE3 и служащей для печати десятичных чисел с плавающей точкой, разрушает содержимое всех ячеек памяти.

«Спектрум», строго говоря, имеет немало «багов» (ошибок) в своем ПЗУ. Как мы указали, функция вычисления целой части числа int нарушает содержимое нулевой ячейки памяти калькулятора, но это нарушение происходит только тогда, когда вычисляется целая часть отрицательного числа. Если аргумент положительный, то ячейка М0 останется нетронутой.

Функция mod_div предназначена для вычисления частного и остатка от деления одного числа на другое, скажем x  и y. Она должна снять со стека два числа, выполнить вычисления и заменить их на стеке двумя новыми числами - остатком x-y*INT(x/y) и частным INT(x/y). К сожалению, процедура ПЗУ, которая выполняет эти действия, совершенно не принимает во внимание тот факт, что int может разрушить М0. В результате, если X - отрицательное число, функция mod_div дает неверный результат [прим.7].

 

Расширение памяти калькулятора.

Введение в работу более шести ячеек памяти основывается на использовании системной переменной MEM. Она расположена по адресу 5С68 (23656). Как Вы знаете, каждая единица хранения на стеке занимает пять байтов, поэтому и каждая ячейка памяти калькулятора должна тоже занимать пять байтов. М0 находится по адресу, который указывает МЕМ. М1 - по адресу (МЕМ)+5; М2 - по адресу (МЕМ)+0А и т.д. Нормально системная переменная МЕМ содержит 5С92 (23698), т.е. указывает на системную переменную МЕМВОТ. Поскольку МЕМВОТ имеет 30 байтов, то в этой области можно разместить ровно 6 ячеек памяти.

Предположим, что Вы хотите иметь 32 ячейки памяти калькулятора (это максимальное возможное количество). Первое, что Вам для этого надо сделать - это выделить 160 байтов свободной памяти. Можно использовать область памяти выше RAMTOP, предварительно установив RAMTOP командой CLEAR. Другой способ - разместить эту память в рабочей области, что можно сделать из машинного кода командой RST 30. Для этого надо разместить в регистре BC необходимое количество байтов (в нашем случае это 160 десят. или A0 шестнадц.) и дать команду RST 30. Процедура отработает, и указанное количество байтов будет отведено. На выходе из этой процедуры регистр DE будет  содержать адрес первого, а регистр HL - адрес последнего байта выделенной памяти. После этого все, что Вам нужно - это перегрузить содержимое DE в системную переменную МЕМ.

Для работы с новыми ячейками памяти служат команды:

·         С6 - для ячейки М6 (store M6);

·         C7 - для ячейки М7 (store M7);

·         DF - для ячейки M1F(store M1F);

 

Вызов данных из этих новых ячеек выполняется командами от Е6 (recall M6) до FF (recall M1F).

Однако есть и ограничения на изменение содержимого переменной МЕМ. Если в ней будет содержаться адрес, отличный от предварительно установленного 5С92, то не будет работать команда калькулятора str$ (код 2Е) и не будет работать процедура печати чисел с плавающей точкой PRINT_FP. Если Вам они необходимы, Вы должны восстанавливать 5С92 в переменной МЕМ перед их использованием.

 

Выполнение переходов в кодах калькулятора.

Для этой цели служит команда jump. Это двухбайтная команда. В первом байте стоит ее код - 33, а за ним идет величина "смещения", указывающая на сколько байтов выполняется переход. Инструкция очень похожа на команду машинного кода JR s, но имеет отличие. Мы уже неоднократно указывали на то, что величина «смещения» отсчитывается в машинных кодах не от адреса, в котором размещена команда JR, а от адреса, в котором находится код следующей за ней операции. В кодах же калькулятора величина «смещения» отсчитывается именно от того адреса, в котором стоит команда jump.

Величина «смещения» задается в двоичной дополнительной форме, поэтому возможны переходы как вперед, так и назад. Вперед на 0…127 (0…7F) байтов и назад на -1…-128 (FF…80) байтов.

 

Обработка условий.

Калькулятор имеет средства для организации вычислений, аналогичных тем, которые выполняет конструкция БЕЙСИКа IF…THEN. Между IF и THEN стоит выражение, которое подлежит проверке и может принимать значения TRUE (истина) и FALSE (ложь). После THEN стоит выражение, которое выполняется в том случае, если условие имеет значение TRUE.

В кодах калькулятора может быть выполнено все то же самое. Кое-что, может быть, даже проще, но есть одно ограничение. Если в БЕЙСИКе после THEN выходным может быть любой оператор, то в кодах калькулятора выходной может быть только команда относительного перехода. Это означает, что можно создавать только конструкции типа IF…THEN GO TO…

Это ограничение можно даже и не рассматривать как ограничение, потому, что достаточно немного изменить структуру программы и Вы опять можете делать что угодно.

Сама же конструкция перехода по условию выражается в калькуляторе командой jump_true. Как и команда jump это двухбайтная команда. Первым идет код операции 00, а за ним величина s («смещение»), задаваемая одним байтом в двоичной дополнительной форме. «Смещение» отсчитывается точно так же, как и в команде jump, рассмотренной выше. Работает команда следующим образом. Со стека снимается верхнее число. Если оно имеет значение TRUE, т.е. не равно нулю, выполняется переход. Если же со стека поступил 0, то переход не выполняется, а байт, выражающий величину «смещения» игнорируется.

Идея использования ненулевых значений для выражения логического значения TRUE и нулевых значений для выражения FALSE - это не просто удобный прием, это основа логики работы «Спектрума». Фактически мы можем зайти так далеко, что признаем TRUE и FALSE иным типом данных, отличным от чисел и стрингов - логическими данными.

Например, команда калькулятора lt_z (меньше, чем 0 код - 36) заменит число, находящееся на вершине стека на TRUE, если это число отрицательное, и на FALSE, если оно положительное.

Другой пример. Команда not (код 30) заменит значение TRUE на вершине стека на FALSE или наоборот. Такой логический подход (вместо числового) упрощает понимание работы программ.

Команда калькулятора not, если на нее смотреть под таким углом  зрения, имеет двойное значение. Во-первых, она меняет логическое значение, находящееся на вершине стека на противоположное. Во-вторых, если содержимое вершины стека рассматривать как число, то после этой команды там будет 0. Таким образом, эта команда может быть использована для обнуления вершины стека, если там был не ноль.

 

Команды «И» (and).

Калькулятор имеет две команды and. Одна для работы с числами - n_and (код 08), а другая - для работы со стрингами - s_and (код 10).

Команда n_and работает достаточно прямолинейно:

      TRUE n_and TRUE   = TRUE

      TRUE n_and FALSE = FALSE

      FALSE n_and FALSE = FALSE

Она может быть использована и в такой форме:

      <число> n_and <логическое значение>

Это же самое можно было бы сделать и в БЕЙСИКе. Попробуйте:

      PRINT (7 AND (X>9))

Подставьте вместо X числа 10 и 8.

 

Вторая команда - s_and. Для нее на вершине стека должен быть стринг, а за ним следовать логическое значение. Команда работает так. Логическое значение удаляется со стека, а стринг заменяется пустым стрингом в том и только в том случае, если логическое значение было FALSE.

Таким образом, мы можем с помощью калькулятора приводить выражения к логическому результату. Другими словами, рассчитывать значение выражений в виде TRUE или FALSE, а затем выполнять переход по условию.

 

B – регистр калькулятора.

Микропроцессор Z-80 имеет много регистров, доступ к которым открывает программирование в машинных кодах. Калькулятор же имеет только один регистр - «B». В нем можно хранить целое число от 0 до 255.

При включении калькулятора по команде RST 28 происходит автоматический перенос содержимого регистра B процессора в регистр B калькулятора. Совершенно аналогично при окончании вычислений в регистр процессора B переносится то, что было в  регистре B калькулятора перед командой end_calc. Более того, «Спектрум» имеет системную переменную под названием B_REG (23655), в которой запоминается то, что было в регистре B калькулятора перед командой end_calc.

Система команд калькулятора имеет команду execute_B (код 38). Она дает команду на исполнение той инструкции, которая содержится в регистре B. Это пример косвенного указания на команду. Таким образом, последовательностью команд:

      RST 28            EF

      execute_B         3B

      end_calc          38

можно выполнить любую из команд калькулятора, если ее код предварительно поместить в B-регистр процессора. Разумеется, это справедливо только для однобайтных команд.

 

Команды, зависимые от регистра «B»

Многие команды калькулятора требуют для своей успешной работы, чтобы в регистре B находилось какое-либо число. Наиболее широко применимы из этих команд команды сравнения: =, <, >, <=, >=, <>. В нашем «Справочнике…» все эти команды оговорены. Обращаем Ваше внимание на то, что в системе команд калькулятора нет кода на загрузку в B-регистр числа. Чтобы это сделать, надо выйти в машинный код, загрузить это число, а затем вновь вернуться в калькулятор.

Например, Вам нужна команда, проверяющая тот факт, что число, находящееся на вершине стека меньше, чем следующее за ним число: n_lt. Эта команда снимает оба числа со стека и меняет их на TRUE, если первое число меньше второго, а в противном случае - на FALSE. Код этой операции 0D, но для того, чтобы она успешно работала, необходимо, чтобы в регистре B также находилось число 0D. Установка там этого числа выполняется с выходом из калькулятора:

Мнемоника         Код   Комментарий

end_calc          38    Выход из калькулятора

LD B,0D           06    Загрузить в регистр В число 0D.

                  0D

RST 28            EF    Вызов калькулятора

n_lt              0D    Сравнение чисел

 

Точно также команда s_eq (код 16), которая проверяет факт равенства двух стрингов, требует, чтобы в регистре B находилось число 16. Таких команд у калькулятора немало. Будьте внимательны при работе с ними.

 

Команды, изменяющие содержимое B-регистра

Выше мы уже говорили, что есть команды, изменяющие содержимое некоторых ячеек памяти калькулятора. Теперь же дополнительно отметим, что есть команды, нарушающие содержимое В-регистра. Это, например, тригонометрические функции sin cos, tan и др. В таблице системы команд (см. «Справочник…») все эти команды оговорены.

 

Организация циклов вычислений

Аналогично программированию в машинном коде система команд калькулятора имеет команду djnz (decrement and jump if not zero - уменьшить содержимое регистра на единицу и, если оно не равно нулю, перейти на заданное количество байтов). Код этой команды 35. Также, как и команды jump и jump_true, она требует после себя указания величины «смещения» s. Эта команда тоже двухбайтная.

Команда работает так. Когда она встречается, происходит уменьшение на 1 содержимого регистра B. Если оно не равно нулю, выполняется переход на заданную величину смещения, а если уже равно нулю, то последующий байт, задающий смещение, игнорируется и вычисления продолжаются в естественном порядке.

 

Помещение чисел на стек

Как Вы уже очевидно поняли, стек калькулятора играет основополагающую роль при проведении вычислений. В этой ситуации совершенно необходимо иметь команду, с помощью которой можно было бы поместить нужное число на стек. Такая команда есть - stk_data (код 34). Это многобайтная инструкция. После кода операции должно идти само число, записанное в так называемой упакованной форме. Мы понимаем, что для начинающего читателя уже надоели бесконечные описания все новых и новых форм представления данных в компьютере, но ничего не поделаешь - таким его сделали создатели. Во всяком случае, это последняя форма, с которой Вам приходится иметь дело, Вы теперь знаете (или можете знать) об этом все.

Правила перевода чисел из пятибайтной интегральной формы в упакованную достаточно неоднозначны и зависят от самого числа, т.е. для равных чисел эти правила различны. Мы приводим справочные таблицы для выполнения такого перевода.

 

 

Упакованная форма целых чисел от 0 до 255.

Если число представить в виде одного байта nn, то шестнадцатеричный код для помещения его на стек: 34 40 В0 00 nn

 

Упакованная форма целых чисел от 0 до 65535.

Если число представить в двухбайтной форме mm, nn, то код для помещения его на стек: 34 40 B0 nn mm

 

Упакованная форма отрицательных целых чисел от -65535 до -1

Если представить в двухбайтной дополнительной двоичной форме -1 как FFFF; -2 как FFFE и т.д., то код: 34 80 B0 FF nn mm

Если Вам трудно сразу вычислить шестнадцатеричный код отрицательного числа, то Вы можете делать так: прибавить к отрицательному числу 65535, затем результат переписать в шестнадцатеричной форме.

 

Помещение на стек пустого стринга

Для этого можно обойтись без команды stk_data соответственно, без упакованной формы. Калькулятор имеет команду stk_zero (код А0), которая помещает на стек целое число 0. Это эквивалент пустого стринга (хотя обратное утверждение справедливо вовсе не всегда).

 

Упакованная форма стрингов длиной до 256 байтов

Если текст Вашего стринга хранится, начиная с какого-то фиксированного адреса, например pp qq и имеет длину nn байтов, шестнадцатеричный код для его помещения на стек: 34 B0 qq pp nn

 

Упакованная форма стрингов произвольной длины

Если адрес pp qq, а длина mm nn, то шестнадцатеричный код 34 B0 qq pp nn mm

 

Произвольные действительные числа

Здесь первым  шагом является перевод числа в пятибайтную интегральную форму, с помощью которой можно представить действительные числа с плавающей точкой. О том, как это делается, мы писали выше. Предположим, что эта форма выглядит так: aa ee dd cc bb

Здесь аа - это экспонента. Далее все зависит от ее величины. Если она находится в диапазоне от 51 до 8F, то упакованная форма определяется по таблице 5.13.6.1., а если аа выходит из этого диапазона, то по таблице 5.13.6.2.

В первом случае Вам надо определить, какая из четырех представленных в таблице форм Вам подходит и определить соответствующее ей значение аа”. Во втором случае также надо в зависимости от величины мантиссы числа подобрать упакованную форму.

                                                     Таблица 5.13.6.1.

Интегральная форма

Код калькулятора

Значение aa

aa ee 00 00 00

34 aa” ee

aa-50

aa ee dd 00 00

34 aa” ee dd

aa-10

aa ee dd cc 00

34 aa” ee dd cc

aa+30

aa ee dd cc bb

34 aa” ee dd cc bb

aa+70

 

                                                     Таблица 5.13.6.2.

Интегральная форма

Код калькулятора

Значение aa

aa ee 00 00 00

34 00 aa” ee

aa-50

aa ee dd 00 00

34 40 aa” ee dd

aa-50

aa ee dd cc 00

34 80 aa” ee dd cc

aa-50

aa ee dd cc bb

34 C0 aa" ee dd cc bb

aa+70

 

Обзор прочих команд калькулятора

Команда read_in предполагалась для ввода символа, поступающего с внешнего канала через подключенный к нему поток X, где X - число на вершине стека. Ее функция - двойная. Во-первых, поток выбирается в качестве текущего, а во-вторых, производится ввод символа, поступающего по этому потоку. Если ввод не происходит, то выдается пустой стринг. Функция была бы очень полезной для чтения символов, поступающих с клавиатуры (аналогично функции БЕЙСИКа INKEY$), но в ПЗУ «Спектрума» содержится нелепая и досадная ошибка. Она проявляет себя, когда X=0 или X=1, а оба эти потока уже назначены для канала "K" (клавиатура). Эта ошибка ведет себя так:

Сначала выбирается канал "К", что вызывает выключение пятого бита системной переменной FLAGS (23611), что означает «готовность к приему новой клавиши». В этом и состоит ошибка, потому что немедленно после этого выполняется попытка ввести символ из канала "К". Это делает подпрограмма, начинающаяся в ПЗУ с адреса 10А8. Она проверяет пятый бит системной переменной FLAGS и немедленно выполняет возврат, если он выключен, что означает «символ поступил». Таким образом, INKEY$ #0 почти обязательно выдает пустой стринг. Единственное исключение, очень маловероятное, происходит когда между выключением пятого бита и его проверкой происходит системное прерывание. Только в этом случае нажатая клавиша будет прочитана. Так очень полезная функция стала бессмысленной из-за наличия в ПЗУ досадной ошибки.

 

Функция get_argt (код 39) вычисляет 2/PI*ASN(SIN(X)). Это довольно странная функция по причине своей бесполезности для пользователя, но к ней обращаются некоторые системные процедуры, «зашитые» в ПЗУ, при вычислении тригонометрических функций.

 

Интересна команда «truncate» (код 3A). Она служит для выделения целой части дробного числа. В этом смысле она несколько похожа на оператор БЕЙСИКа INT и команду калькулятора int (код 27), но если INT округляет дробь всегда вниз, то «truncate» отсекает дробную часть, т.е. округляет «к нулю». Пример сравнения дан в таблице.

X

int X

truncate X

5.3

5

5

5.8

5

5

-5.3

-6

-5

-5.8

-6

-5

 

Команда e_to_fp (код 3С) совершенно бесполезна потому, что содержит грубую ошибку, делающее невозможным ее использование из калькулятора.

По идее она предназначалась для того, чтобы переводить числа из нормализованной формы в форму с плавающей точкой, т.е. вычислять X*10A, где X - действительное число, находящееся на вершине стека, а А - содержимое регистра А микропроцессора.

Но при работе калькулятора регистр А занят другими вычислениями и потому эта команда не дает правильного результата.

Если Вам необходимо выполнить такое действие, то Вы можете это сделать из машинного кода, не входя в калькулятор, по команде CALL 2D4F. Результат будет отправлен на вершину стека калькулятора.

 

Команду restack (код 3D) можно представить как противоположность int. Она преобразует целые числа в действительные.

      00 00 04 00 00 - целое число 4

      83 00 00 00 00 - действительное число 4

Эта команда переводит из одной формы в другую.

 

Калькулятор имеет еще целую группу команд, предназначенных для генерации полиномов Чебышева. Они применяются самим же калькулятором для вычисления алгебраических и тригонометрических функций. Дотошный пользователь, впрочем, может попробовать использовать их для создания процедур вычисления каких-то своих нужных ему функций.

На этом мы закончим рассмотрение работы с калькулятором по команде RST 28, но во второй части – «Практикум по программированию в машинных кодах Z-80» мы еще к нему вернемся и рассмотрим конкретные примеры применения калькулятора.

 

5.13.7. Команда RST 30.

Эта команда служит для создания (резервирования) свободной области памяти в рабочей области БЕЙСИКа. При работе в БЕЙСИКе эта процедура активно используется как системная. Без нее не обходятся такие операции как ввод строки, редактирование программы. Перед вызовом этой процедуры в регистровой паре ВС должна содержаться длина резервируемой области памяти в байтах.

 

5.13.8. Команда RST 38.

Эта процедура служит для обработки маскируемых прерываний, о которых мы будем еще говорить немного позже. Во-первых, она выполняет наращивание системной переменной FRAMES (23672…23674), которая исполняет роль «внутренних часов» «Спектрума» и, во-вторых, обеспечивает сканирование клавиатуры в поисках нажатой клавиши каждую пятидесятую долю секунды.

 

5.13.9. Замечания к использованию команд обращения к ПЗУ.

ПЗУ компьютера "Спектрум" содержит 16-ти килобайтную программу-монитор, обеспечивающую функционирование компьютера. Она включает в себя: интерпретатор БЕЙСИКа, программы, обеспечивающие ввод/вывод, связь с внешними устройствами, программу-калькулятор, различные таблицы, в том числе набор 96-ти символов и т.п.

Монитор состоит из сотен процедур, многими из которых Вы можете активно пользоваться, что очень сильно сокращает время, необходимое для разработки программ и уменьшает их размер. К использованию процедур, содержащихся в ПЗУ, фирмы, выпускающие компьютеры, практикуют различный подход.

В компьютерах, имеющих большую оперативную память типа «Amiga», «Atari ST» и т.п. использование в программах пользователей обращений к системному ПЗУ считается дурным тоном. Разработчики компьютеров оставляют за собой право постоянно совершенствовать ПЗУ, а большой объем оперативной памяти всегда дает возможность программистам размещать необходимые им процедуры в ОЗУ. Если бы коммерческие программы имели обращения к ПЗУ, то многочисленные владельцы компьютеров с новыми моделями компьютеров не смогли бы ими пользоваться, т.е. использование в коммерческих программах многочисленных обращений к ПЗУ может серьезно нарушить совместимость программного обеспечения, если ПЗУ постоянно или периодически дорабатывается фирмой.

В Синклер-компьютерах реализован другой подход. Малый объем оперативной памяти предполагает не только возможность, но и необходимость самого широкого использования процедур ПЗУ в пользовательских программах. ПЗУ не только открыто для эксплуатации, более того, К.Синклер включил в систему команд команды группы RST, делающие этот доступ простым. Если посмотреть с таких позиций, то становится понятным, как и почему для компьютера с такой скромной памятью созданы тысячи изумительных программ, многие из которых по своей идеологии превосходят созданное для гораздо более могучих машин.

С другой стороны, здесь фирме пришлось заплатить за это отказом от доработки ПЗУ. Выше мы говорили о том, что ПЗУ имеет немало ошибок и теперь Вы должны понять, что это нельзя серьезно ставить в вину фирме. Фирма могла бы многократно доработать ПЗУ, как и поступают все фирмы, но страдали бы от этого потребители, т.к. от этого нарушилась бы совместимость компьютеров и программного обеспечения.

Конечно, от программиста это требует определенной внимательности, зато миллионы простых пользователей по всему миру очень выиграли от скромности К.Синклера. Это еще раз напоминает нам о том, что Синклер-компьютеры очень «дружественны» к потребителю, что и обусловило их невероятную популярность.

Справедливости ради, надо сказать, что есть еще третий путь, который например был реализован в компьютерах «Commodore». Здесь фирма тоже закрывает ПЗУ и не рекомендует использовать его процедуры напрямую, периодически внося в них изменения. Но зато она выделила небольшой участок ПЗУ в качестве своеобразного диспетчера. Этот блок называется «керналь». В нем хранятся адреса основных процедур ПЗУ. Поэтому если при доработке ПЗУ что-то изменяется в адресах процедур, эти изменения тут же вносятся в «керналь», который всегда находится в одном и том же месте. Так обеспечивается совместимость программного обеспечения между старыми и новыми моделями. Программы пользователя не входят в ПЗУ напрямую, а входят в «керналь», откуда их переправляют туда, куда надо. К сожалению, в «Спектруме» такого блока нет.

«ИНФОРКОМ» получает массу писем с изложением новых и все более изощренных способов доработки ПЗУ. До сих пор многие энтузиасты прилагают свою энергию для русификации компьютера. Каждый делает это так, как ему заблагорассудится - стандартов нет, да они вряд ли и будут. Помните, что такая «доработка» серьезно нарушает совместимость компьютеров. Вы не сможете воспользоваться чужой программой, созданной на «русифицированном» компьютере, отличном от Вашего. Разработанные Вами русскоязычные программы также будут иметь коммерческую ценность близкую к нулевой, если при русификации Вы опирались на свое измененное ПЗУ. Помните, что всегда есть возможность русификации при размещении русского шрифта в оперативной памяти. Мы об этом неоднократно писали в разработке «Большие возможности Вашего Спектрума» и в выпусках «ZX-РЕВЮ». Практикуют и другие доработки (например, встраивают в ПЗУ программы обслуживания внешних портов). Мы понимаем, что остановить творческий поиск невозможно, но по крайней мере не позволяйте никому брать дополнительную плату за «доработки» ПЗУ и выдавать их за преимущества. Относитесь к ним критически.

Желающим действительно сделать ценные разработки, рекомендуем делать это путем подключения замещающего ПЗУ («теневого»), как это и делается в наиболее практичных периферийных устройствах.

 




СОДЕРЖАНИЕ:


  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
AyHits - Max и музыка :)
Программирование - как накодить индикаторы музыкального сопрацессора в чанках.
Readme.txt - Приветствую всех читателей!
Enjoy - Рассказ "Учитесь стрелять".
Post Box - предыдущий номер вызвал бурную реакцию в массах.

В этот день...   23 ноября