ОШИБКИ ПЗУ
Обзор по материалам зарубежной печати
В этой статье рассмотрены ошибки и неточности, имеющиеся в ПЗУ "СПЕКТРУМА". Может быть не все они, строго говоря, и являются ошибками, а просто особенностями компьютера, но о них надо знать и уметь обходить в тех случаях, когда они могут помешать нормальной работе.
1. Ограничение по использованию регистровой пары IY.
Эта ошибка (неточность) связана с тем, как в "Спектруме" обрабатываются маскируемые прерывания. Вы знаете, что обычно компьютер 50 раз в секунду приостанавливает исполнение своей текущей программы и обращается по адресу 0038H = 56 DEC для запуска процедуры обработки маскируемого прерывания. Эта процедура увеличивает на единицу показания системных часов компьютера (трехбайтную системную переменную FRAMES) и вызывает подпрограмму сканирования клавиатуры в поисках нажатой клавиши. Благодаря этому и возможен диалог между Вами и компьютером.
Ошибка связана с тем, что увеличение на единицу системных часов производится некорректно. Младшие два байта увеличиваются командой INC HL и с ними все в порядке, но когда они переполняются и надо увеличить старший байт, расположенный по адресу 5С7АН (33674), для этого используется команда процессора
INC (IY+40)
Программист, который писал эту процедуру, предполагал, что во время ее работы в регистровой паре IY должно быть значение 5С3АН (23610) - указание на системную переменную ERR-NR. в принципе так оно и должно бы быть.
Когда вы вызываете подпрограмму, написанную в машинном коде с помощью RANDOMIZE USR nn, процедура калькулятора, занимающаяся обработкой функции USR nn (она расположена по адресу 34В3Н) автоматически сохраняет на стеке адрес 2D2EH. Поэтому, когда Вы вернетесь по RET из своей подпрограммы, то попадете сначала в адрес 2D2BH (процедура STACK_BC). Здесь восстанавливается нормальное содержимое пары IY = 5C3AH.
Таким образом, у Вас все было бы в полном порядке, если бы не одно "но". Ведь во время работы самой Вашей процедуры тоже может пройти системное прерывание. И если Вы изменили содержимое IY, то вместо приращения системных часов получите что угодно.
Ошибка ПЗУ состоит в том, что в процедуре обработки маскируемого прерывания следовало бы выставлять в IY значение 5С3А, а только потом наращивать старший байт системных часов, а перед выходом восстанавливать в IY Ваше значение.
Достаточно обидно иметь в своем распоряжении регистровую пару IY и не иметь возможности ею воспользоваться. И пользоваться ею можно, если при этом соблюдать некоторые меры предосторожности.
1. Если Вы намерены использовать регистровую пару IY, то возьмите себе за правило прежде, чем что-либо изменять в этой паре, отключать системные прерывания командой процессора DI - DISABLE INTERRUPTS, а перед выходом из процедуры восстанавливать их командой EI - ENABLE INTERRUPTS.
2. Может быть, Вам захочется даже создать свою собственную процедуру для обработки прерываний.
2. Особенности регистровой пары H'L' (альтернативной).
Эта ошибка коснется тех, кто программирует в машинных кодах, а еще точнее - тех, кто в своих программах объединяет БЕЙСИК и машинный код, а так поступают очень многие.
Они пишут логику программы на БЕЙСИКе, а операции, требующие большой скорости, пишут в машинном коде. Вероятность споткнуться об эту ошибку практически стопроцентная. Рано или поздно она испортят Вам кровь, хотя избежать ее очень просто.
В двух словах, суть состоит в том, что если Ваша процедура, написанная в маш. коде изменит содержимое регистровой пары H'L' (альтернативной), то в БЕЙСИК Вы уже не вернетесь и программа сбросится или зависнет.
Почему так происходит? Чтобы ответить на этот вопрос, надо знать, как происходит вызов пользовательских машиннокодовых процедур. Вы конечно знаете, что они вызываются командой RANDOMIZE USR nn, где nn - адрес ее старта. В принципе вместо RANDOMIZE USR может использоваться RESTORE USR, PRINT USR и т.п., сейчас нам это все равно. Суть состоит в том, что когда интерпретатор БЕЙСИКа встретит USR nn, то он должен рассчитать чему равно nn и для этого вызывает встроенный калькулятор. Это делает процедура ПЗУ, которая занимается расчетом выражений. По адресу 2756Н она содержит вызов калькулятора, а по адресу 2758Н - выход из калькулятора.
При вводе в калькулятор содержимое вершины машинного стека (а это есть адрес возврата из калькулятора) запоминается в альтернативной регистровой паре H'L'. Это делает процедура, находящаяся по адресу 3362H.
Затем процедура калькулятора, занимающаяся обслуживанием функции USR nn (34B3H) помещает на вершину стека адрес 2D2Bh и адрес "nn".
По команде RET со стека снимается адрес "nn" - выполняется переход в Вашу машиннокодовую процедуру.
Когда она отработает, по ее команде RET со стека снимется адрес 2D2BH и выполнится переход по этому адресу в ПЗУ. Мы писали, рассматривая предыдущую ошибку о том, что расположенная там процедура восстанавливает "стандартное" значение в регистровой паре IY, но она ничего не делает, чтобы восстановить содержимое альтернативной пары H'L', a ведь она тоже могла быть нарушена при исполнении пользовательской процедуры.
Таким образом, исполнение пользовательской процедуры в машинных кодах начинается с вызова системного калькулятора для расчета адреса старта, а кончается выходом из него. Выход из калькулятора производится помещением на стек того, что хранилось в H'L' и переходом по этому адресу. Это делает процедура, обслуживающая команду калькулятора end-calc, расположенная по адресу 369ВН. И содержаться в этот момент в H'L' может только адрес 2758H.
Итак, если Ваша процедура нарушает содержимое регистровой пары H'L' (альтернативная) и Вы рассчитываете вернуться в БЕЙСИК, то потрудитесь перед выходом из процедуры восстановить в этой паре значение 2758Н, иначе ничего у Вас не получится.
3. Особенности пользовательской функции FN.
Недоразумение, связанное с пользовательскими функциями может коснуться и тех, кто программирует на БЕЙСИКе и тех, кто программирует в машинном коде, но скорее всего с ним столкнутся опять те же энтузиасты, которые используют и БЕЙСИК и машинный код одновременно, причем пользуются для передачи параметров из БЕЙСИКа в маш. код пользовательскими функциями. Это весьма удобный и прогрессивный метод и мы подробно осветили его в первом томе, посвященном графике - "Элементарная графика".
Суть этой ошибки состоит в том, что если во время исполнения пользовательской функции произойдет какое-либо перемещение программной области БЕЙСИКа вверх или вниз, то будет выдано сообщение C; Nonsense in BASIC.
Конечно, не так-то просто выполнить перемещения БЕЙСИК-области и "нарваться" на такую ошибку, но это вполне возможно, если Вы используете USR внутри вашей функции.
На практике это может означать вот что. Если Вы, например, зададите пользовательскую функцию FN D(m,n) = USR NN, предназначенную для удаления из БЕЙСИК-программы строк m и n, то она у вас работать не сможет.
Причина появления ошибки - в статическом характере системной переменной CH ADD (23645=5C5DH), которая при работе интерпретатора содержит адрес следующего интерпретируемого символа. На время исполнения пользовательской функции она "сохраняет" свое состояние на стеке и, если во время работы функции все адреса, имеющие отношения к БЕЙСИКУ "поплывут", то после окончания ее работы интерпретатор не сможет продолжить исполнение БЕЙСИК-программы.
4. Ошибка деления.
Эта ошибка встречается настолько часто, что о ней даже и не говорят, как об ошибке деления, а считают ее ошибкой округления. Однажды в разделе "ФОРУМ" мы отвечали на письмо читателей, связанное с этой ошибкой (см. '^-РЕВЮ-91, с. 222). Кратко проиллюстрировать ее можно на примере:
IF 1/2 <> 0.5 THEN PRINT "Ku-Ku"
Попробовав, Вы сами убедитесь, что 1/2 не равна 0.5.
Конечно, в инженерных расчетах следует вводить понятие точности вычислений и сравнивать действительные числа между собой только в пределах установленной точности, но все-таки где-то что-то в ПЗУ не совсем то, раз компьютер выдает такие непонятные результаты.
Ошибка содержится в процедуре калькулятора, выполняющей деление и связана с неправильным округлением результата. Не вдаваясь в подробности как происходит деление двух действительных чисел, скажем только, что процедура работает с 32-мя битами, что и определяет точность числа. Но "за кадром" она учитывает еще 33-ий и 34-ый биты для правильного округления. Так вот, именно при добавлении 34-го бита и происходит ошибка -он не учитывается из-за неправильного перехода в адресе 3200Н. Там записана команда JR Z, 31E2, а должно быть JR Z, 31DB.
Наилучший путь обхода - установить требуемую точность для своих расчетов, например:
LET eps = 0.0000001 и затем вместо IF X = Y
употреблять: IF (X-Y) < eps
5. Ошибка "-65536".
Это довольно известная ошибка. Она легко вызывается оператором:
PRINT INT(-65536) который дает -1.
Причина скрыта в процедурах калькулятора, которые работают с пятибайтной формой представления чисел. Одни из них считают, что целые числа могут быть только в диапазоне от-65535 до +65535.
Некоторые, например процедура, выполняющая сложение, полагают, что и -65536 -тоже допустимое целое число, в частности, возможно, что сложив -65000 и -536, можно получить число -65536, которое внутри компьютера будет представлено не как действительное, а как целое число.
Процедура же INT - одна из тех, которые " разоблачают" эту двусмысленность. В известной книге Я. Логана и Ф. О'Хары "The Complete SPECTRUM ROM Disassembly" подробному разбору этой ошибки посвящена целая глава приложения. Объяснения достаточно мудреные для непрофессионалов и мы отошлем тех, кому эти знания существенно важны, к этой книге.
6. Ошибка оператора PLOT.
Может быть, это и не ошибка в том смысле, что к фатальным последствиям она не приведет. Дело в том, что если в операторе PLOT X,Y Вы зададите координаты X и Y отрицательными числами, то все равно получите точку на экране.
На самом деле получается так, что команда PLOT печатает точку не в координатах X и Y, а в координатах ABS(X),ABS(Y). Причина - в том, что процедура ПЗУ PLOT (22DCH) вызывает процедуру, перемещающую целые числа из вершины стека калькулятора в регистровую пару BC (процедуру STK_TO_BC - 2307H). Но после того, как STK_TO_BC отработает, не учитывается, что она еще поместила в регистровую пару DE знаки тех чисел, которые пошли в BC, и при возвращении в процедуру PLOT надо было бы проверить DE на знак, что не было сделано.
7. Ошибка первого экрана в компьютерах 128К.
Мы уже писали об этой ошибке в предыдущем выпуске "ZX-РЕВЮ" на стр. 156-159. Напомним, что 128-килобайтные машины имеют две экранных области памяти. Нулевой экран расположен в обычных адресах - 4000Н, а дополнительный первый экран - в адресе 7С000Н. Их можно переключать. Команда POKE 23388,24 включит первый экран, а команда POKE 23388,16 включит нулевой экран.
Ошибка происходит в тех случаях, когда пользователь работает с верхними областями памяти этой машины, как с электронным RAM-диском и сохраняет в ней данные в виде многочисленных файлов. Файлы "располагаются" в памяти снизу вверх и в это же время сверху вниз в памяти растет каталог этих файлов. В компьютере ничего не сделано для того, чтобы каталог или сами файлы не "перехлестнулись" с первым (дополнительным) экраном.
Чтобы этого не произошло, не храните более 216 файлов, как бы малы они не были, чтобы экран не нарушился каталогом. Вам также нельзя иметь в сумме более 64K данных в этих файлах, иначе экран-1 будет испорчен самими файлами.
(Окончание в следующем выпуске).