Последнее устройство, которое мы опишем в этом томе - арифметический сопроцессор Intel 8087/80287/80387. Это устройство подключено непосредственно к центральному процессору и предназначено для выполнения операций над числами в формате с плавающей точкой (вещественные числа) и длинными целыми числами.
Арифметический сопроцессор значительно (в десятки раз) ускоряет вычисления, связанные с вещественными числами. Он может вычислять такие функции, как синус, косинус, тангенс, логарифмы и т.д. Разумеется, что с помощью сопроцессора можно выполнять и простейшие арифметические операции сложения, вычитания, умножения и деления.
Основная область применения арифметического сопроцессора - научные расчеты и машинная графика. Некоторые пакеты САПР, например, Autocad версии 10, отказывается работать, если в машине отсутствует сопроцессор. Более современный процессор Intel 80486 содержит встроенный арифметический сопроцессор, совместимый с 80387 (и даже немного более мощный).
Сопроцессор запускается центральным процессором. После запуска он выполняет все вычисления самостоятельно и параллельно с работой центрального процессора. Если центральный процессор выдает очередную команду сопроцессору в момент времени, когда тот еще не закончил выполнение предыдущей команды, центральный процессор переводится в состояние ожидания. Если же сопроцессор ничем не занят, центральный процессор, выдав команду сопроцессору, продолжает свою работу, не дожидаясь завершения вычисления. Впрочем, есть специальные средства синхронизации (команда FWAIT).
Как программировать сопроцессор?
Команды, предназначенные для выполнения сопроцессором, записываются в программе как обычные машинные команды центрального процессора. Но все эти команды начинаются байта, соответствующего команде центрального процессора ESC.
Встретив такую команду, процессор передает ее на выполнение сопроцессору, а сам продолжает выполнение программы со следующей команды.
Ассемблерные мнемоники всех команд сопроцессора начинаются с буквы F, например: FADD, FDIV, FSUB и т.д. Команды сопроцессора могут адресоваться к операндам, аналогично обычным командам центрального процессора. операндами могут быть либо данные, расположенные в основной памяти компьютера, либо внутренние регистры сопроцессора.
Возможны все виды адресации данных, используемые центральным процессором.
Прежде чем начать обсуждение команд, выполняемых сопроцессором, приведем форматы используемых данных. Как мы уже говорили, сопроцессор может работать либо с данными в формате с плавающей точкой, либо с целыми числами. В следующем разделе мы рассмотрим используемые форматы чисел с плавающей точкой или форматы вещественных чисел.
Прежде чем говорить о форматах вещественных числе, используемых сопроцессором, вспомним о числах с плавающей точкой, встречающихся в научных расчетах.
В общем виде эти числа можно записать следующим образом:
(знак)(мантисса)*10(знак)(порядок)
Например: -1.35*105
Здесь знак - это минус, мантисса - 1.35, порядок - 5. Порядок тоже может иметь знак. В этом представлении чисел для вас вряд ли есть что либо новое. Вспомним также такое понятие, как норамализованное представление чисел:
В чем преимущества использования нормализованных чисел? В том, что для фиксированной разрядной сетки числа (т.е. для фиксированного количества цифр в числе) нормализованные числа имеют наибольшую точность. Кроме того, нормализованное представление исключает неоднозначность - каждое число с плавающей точкой может быть представлено различными (ненормализованными) способами:
123.5678*105 = 12.35678*106 = 1.235678*107 = 0.1235678*108
Для тех, кто программировал на языках высокого уровня, знакомо следующее представление чисел с плавающей точкой:
(знак)(мантисса)E(знак)(порядок)
Например, -5.35E-2 означает число -5.35*10-2. Такое представление называется научной нотацией.
Сопроцессор 8087/80287/80387 может работать с вещественными числами в трех форматах:
Эти числа занимают в памяти, соответственно, 4, 8
или 10 байтов:
Одинарная точность 1 бит 8 бит 23 бита ---T-------T--------------------¬ |Зн|Порядок| Мантисса | L--+-------+--------------------- Двойная точность 1 бит 11 бит 52 бита ---T---------T--------------------------------¬ |Зн| Порядок | Мантисса | L--+---------+--------------------------------- Расширенная точность 1 бит 15 бит 64 бита ---T-------------T------------------------------------¬ |Зн| Порядок | Мантисса | L--+-------------+-------------------------------------
В любом представлении старший бит "Зн" определяет знак вещественного числа:
Все равные по абсолютному значению положительные и отрицательные числа отличаются только этим битом. В остальном числа с разным знаком полностью симметричны. Для представления отрицательных чисел здесь не используется дополнительный код, как это сделано в центральном процессоре.
Арифметический сопроцессор работает с нормализованными числами, поэтому поле мантиссы содержит мантиссу нормализованного числа.
Так как здесь используется двоичное представление чисел, сформулируем определение нормализованного числа для двоичного представления:
Так как для нормализованного двоичного числа целая часть всегда равна единице, то эту единицу можно не хранить. Именно так и поступили разработчики арифметического сопроцессора - в форматах одинарной и двойной точности целая часть мантиссы не хранится. Таким образом экономится один бит памяти.
Для наглядности представим мантиссу числа в следующей форме:
n.nnnnnnnnnn...n
Здесь символом n обозначается либо 0, либо 1. Нормализованные числа в самой левой позиции содержат 1, поэтому их можно изобразить еще и в таком виде:
1.nnnnnnnnnn...n
Представление с расширенной точностью используется сопроцессором для выполнения всех операций. И даже более - все операции с числами сопроцессор выполняет над числами только в формате с расширенной точностью. В этом формате хранится и "лишний" бит целой части нормализованного числа.
Основная причина использования для вычислений расширенной точности - предохранение программы от возможной потери точности вычислений, связанной с большими различиями в порядках чисел, участвующих в арифметических операциях.
Поле порядка - это степень числа 2, на которую умножается мантисса, плюс смещение, равное 127 для одинарной точности, 1023 - для двойной точности и 16383 - для расширенной точности.
Для того, чтобы определить абсолютное значение числа с плавающей точкой, можно воспользоваться следующими формулами:
Знак числа, как мы уже говорили, определяется старшим битом.
Приведем конкретный пример. Пусть мы имеем число с одинарной точностью, которое в двоичном виде выглядит следующим образом:
1 01111110 11000000000000000000000
Для этого числа знаковый бит равен 1 (отрицательное число), порядок равен 126, мантисса - 11 (в двоичной системе счисления).
Значение этого числа равно:
1.11 * 2(126-127) = -1.75 * 2-1 = -0,875
Рассмотрим теперь различные особые случаи представления вещественных чисел.
Для большей наглядности сведем все возможные представления вещественных чисел в таблицу:
Положительный нуль --T---------T------------------¬ |0| 0...0 | 0...0 | L-+---------+------------------- Отрицательный нуль --T---------T------------------¬ |1| 0...0 | 0...0 | L-+---------+------------------- Наименьшее положительное число --T---------T------------------¬ |0| 0...01 | 0...0 | L-+---------+------------------- Наибольшее отрицательное число --T---------T------------------¬ |1| 0...01 | 0...0 | L-+---------+------------------- Наибольшее положительное число --T---------T------------------¬ |0| 11...10 | 1...1 | L-+---------+------------------- Наименьшее отрицательное число --T---------T------------------¬ |1| 11...10 | 1...1 | L-+---------+------------------- Положительная бесконечность --T---------T------------------¬ |0| 1...1 | 0...0 | L-+---------+------------------- Отрицательная бесконечность --T---------T------------------¬ |1| 1...1 | 0...0 | L-+---------+------------------- Нечисло --T---------T------------------¬ |1| 1...1 | х...х | L-+---------+------------------- Неопределенность --T---------T------------------¬ |1| 1...1 | 10...0 | L-+---------+-------------------
Арифметический сопроцессор наряду с вещественными числами способен обрабатывать и целые числа. Он имеет команды, выполняющие преобразования целых чисел в вещественные и обратно.
Возможно четыре формата целых чисел:
Целое число занимает два байта. Его формат полностью соответствует используемому центральным процессором. Для представления отрицательных чисел используется дополнительный код. Короткое целое и длинное целое имеют аналогичные форматы, но занимают, соответственно, 4 и 8 байтов.
Упакованное десятичное число занимает 10 байтов. Это число содержит 18 десятичных цифр, расположенных по две в каждом байте. Знак упакованного десятичного числа находится в старшем бите самого левого байта. Остальные биты старшего байта должны быть равны 0.
Существуют команды сопроцессора, которые преобразуют числа в формат упакованных десятичных чисел из внутреннего представления в расширенном вещественном формате. Если программа делает попытку преобразования в упакованный формат денормализованных чисел, нечисел, бесконечности и т.п., в результате получается неопределенность. Неопределенность в упакованном формате представляет из себя число, в котором два старших байта содержат единицы во всех разрядах. Содержимое остальных восьми байтов произвольно. При попытке использовать такое упакованное число в операциях фиксируется ошибка.
Мы подробно рассмотрели формат представления вещественных чисел и отметили, что в этом формате для представления отрицательных чисел используется специальный знаковый бит. Для целых чисел используется дополнительный код.
В дополнительном коде положительные числа содержат нуль в самом старшем бите числа:
0XXX XXXX XXXX XXXX
Для получения отрицательного числа в дополнительном коде из положительного надо инвертировать каждый бит числа и затем прибавить к числу единицу.
Например, число +5 в дополнительном коде выглядит следующим образом:
0000 0000 0000 0101 = +5
Для получения числа -5 вначале инвертируем значение каждого бита:
1111 1111 1111 1010
Теперь прибавим к полученному числу +1:
1111 1111 1111 1011 = -5
Приведем возможные варианты представления целых чисел:
Нуль -------------------¬ | 0...0 | L------------------- Наименьшее положительное число -------------------¬ | 0...1 | L------------------- Наибольшее отрицательное число -------------------¬ | 1...1 | L------------------- Наибольшее положительное число -------------------¬ | 01...1 | L------------------- Наименьшее отрицательное число -------------------¬ | 10...01 | L------------------- Неопределенность -------------------¬ | 10...00 | L-------------------
Упакованное десятичное число имеет следующий вид:
|1-й байт | Девять байтов | +--T------+---T---T---T---T---+ |Зн|000000|n17|n16|...|n1 |n0 | L--+------+---+---+---+---+----
На этом рисунке n0...n17 означают разряды десятичного числа. Они могут изменяться в пределах от 0000 до 1001, т.е. от 0 до 9 в десятичной системе счисления.
Теперь, после того как мы рассмотрели форматы данных, с которыми может работать арифметический сопроцессор, можно перейти к изучению внутренних регистров сопроцессора.
Арифметический сопроцессор содержит восемь численных 80-битовых регистров, предназначенных для хранения промежуточных результатов вычислений, регистра управления, регистра состояния, регистра тегов, регистра указателя команды и регистра указателя операнда.
Мы будем обозначать численные регистры как ST0 - ST7. Они приведены на следующем рисунке:
80 бит ----------------------------------------------------¬ ST0 | | +---------------------------------------------------+ ST1 | | +---------------------------------------------------+ ST2 | | +---------------------------------------------------+ ST3 | | +---------------------------------------------------+ ST4 | | +---------------------------------------------------+ ST5 | | +---------------------------------------------------+ ST6 | | +---------------------------------------------------+ ST7 | | L----------------------------------------------------
Численные регистры используются как стек. Регистр состояния в поле ST содержит номер численного регистра, являющего вершиной стека. При выполнении команд в качестве операнда могут выступать численные регистры. В этом случае номер указанного в команде регистра прибавляется к содержимому поля ST регистра состояния и таким образом определяется используемый регистр. Большинство команд после выполнения увеличивают поле ST регистра состояния, как бы записывая результаты своей работы в стек численных регистров.
Вы можете использовать регистры как массив, но в этом случае необходимо заботится о постоянстве поля ST регистра состояния, так как в противном случае номера численных регистров будут изменяться.
Этот регистр разделен на восемь двухбитовых полей, которые мы обозначим как TAG0...TAG7. Каждое поле относится к своему численному регистру:
-----T----T----T----T----T----T----T----¬ |TAG0|TAG1|TAG2|TAG3|TAG4|TAG5|TAG6|TAG7| L----+----+----+----+----+----+----+-----
Поля регистра тегов классифицируют содержимое
"своего" численного регистра:
00 |
регистр содержит действительное
ненулевое число; |
01 |
в регистре находится нуль; |
10 |
регистр содержит недействительное
число (нечисло, бесконечность, неопределенность);
|
11 |
пустой неинициализированный регистр. |
Например, если все регистры сопроцессора были пустые, а затем в стек численных регистров было занесено одно действительное ненулевое значение, содержимое регистра тегов будет 3FFFh.
Регистр управления для сопроцессора 8087 показан на следующем рисунке:
15-13 12 11-10 9-8 7 6 5 4 3 2 1 0 ---------T-T-----T-----T---T---T---T---T---T---T---T---¬ |XXXXXXXX|IC| RC | PC |IEM|XXX|PM |UM |OM |ZM |DM |IM | L--------+--+-----+-----+---+---+---+---+---+---+---+----
Регистр управления сопроцессоров 80287/80387 и сопроцессора, входящего в состав процессора 80486, имеет аналогичный формат, за исключением того, что бит 7 в нем не используется:
15-13 12 11-10 9-8 7-6 5 4 3 2 1 0 ---------T-T-----T-----T--------T---T---T---T---T---T---¬ |XXXXXXXX|IC| RC | PC |XXXXXXXX|PM |UM |OM |ZM |DM |IM | L--------+--+-----+-----+--------+---+---+---+---+---+----
Биты 0...5 - маски особых случаев. Особые случаи иногда возникают при выполнении команд сопроцессора, например, при делении на нуль, переполнении и т.д. Если все биты масок особых случаев равны нулю, особый случай вызывает прерывание центрального процессора INT 10h (обратите внимание, что это прерывание используется BIOS для работы с дисплейным адаптером). Если же особые случаи замаскированы установкой соответствующих битов в единичное состояние, прерывание не вырабатывается, а в качестве результата возвращается особое значение - бесконечность, нечисло и т.д.
Приведем таблицу масок особых случаев:
IM |
маска недействительной операции; |
DM |
маска денормализованного результата; |
ZM |
маска деления на нуль; |
OM |
маска переполнения; |
UM |
маска антипереполнения; |
PM |
маска особого случая при неточном
результате; |
IEM |
маскирование одновременно всех особых
случаев вне зависимости от установки битов 0...5
регистра управления, этот бит действителен
только для сопроцессора 8087 |
Подробнее особые случаи и условия их возникновения будут описаны позже, когда мы займемся ошибками при выполнении команд в сопроцессоре.
Поле PC управляет точностью вычислений в
сопроцессоре:
00 |
использование расширенной точности,
этот режим устанавливается при инициализации
сопроцессора; |
10 |
округление результата до двойной
точности; |
00 |
округление результата до одинарной
точности. |
Искусственное ухудшение точности вычислений не приводит к ускорению работы программы. Режимы с пониженной точностью предназначены для эмуляции процессоров, использующих двойную и одинарную точность, соответственно.
Двух битовое поле RC задает режим округления при
выполнении операций с вещественными числами:
00 |
округление к ближайшему числу, этот
режим устанавливается при инициализации
сопроцессора; |
01 |
округление в направлении к
отрицательной бесконечности; |
10 |
округление в направлении к
положительной бесконечности; |
11 |
округление в направлении к нулю. |
На следующих рисунках демонстрируются перечисленные выше режимы округления. Символами "o" обозначены точные значения вещественных чисел, символами "x" приближенные значения. Стрелки "<<" и ">>" указывают направление округления. В центре линии расположен нуль числовой оси, на ее левом и правом конце - отрицательная и положительная бесконечности.
Округление в направлении к ближайшему числу. -беск.<-o-<<-x-------o---- 0 -----o-----x->>-o---->+беск. Округление в направлении к отрицательной бесконечности. -беск.<-o-<<-x-------o---- 0 -----o--<<---x-o---->+беск. Округление в направлении к положительной бесконечности. -беск.<-o-x-->>-----o---- 0 -----o------x->>-o---->+беск. Округление в направлении к нулю. -беск.<-o-x-->>-----o---- 0 -----o--<<----x-o---->+беск.
Для наибольшего уменьшения ошибок вычислений наиболее целесообразно использовать режим округления в направлении к ближайшему числу. Режим округления в направлении к нулю используется при моделировании целочисленной арифметики.
Остальные два режима округления используют в интервальной арифметике. Для получения наиболее точного результата каждая команда (операция) выполняется два раза - первый раз с округлением в направлении к отрицательной бесконечности, второй раз - в направлении к положительной бесконечности. Точный результат лежит между полученными значениями. Заметьте, что здесь речь идет только об отелных операциях, но не о том, чтобы выполнить всю программу вычислений вначале с одним режимом округления, а затем с другим.
Поле IC регистра управления предназначен для управления бесконечностью:
0 проективный режим; 1 афинный режим.
В проективном режиме существует только одна бесконечность, она не имеет знака:
Бесконечность -----------------> <-----------------¬ | | | | | | L---------------- 0 ------------------
В афинном режиме имеется две бесконечности -
положительная и отрицательная:
-бесконечность + бесконечность <-------------------------- 0 ---------------------------->
Афинный режим допускает выполнение многих операций с бесконечностями - сложение, умножение и т.д.
Поля регистра состояния сопроцессора 8087 показаны на следующем рисунке:
15 14 13-11 10 9 8 7 6 5 4 3 2 1 0 ---T-T-----T-T-T-T-T-T-T---T-T-T-T--¬ |B |C3| ST |C2|C1|C0|IR|XX|PE|UE |OE|ZE|DE|IE| L--+--+-----+--+--+--+--+--+--+---+--+--+--+---
Регистр состояния сопроцессоров 80287/80387 и сопроцессора, входящего в состав процессора 80486, имеет немного другой формат:
15 14 13-11 10 9 8 7 6 5 4 3 2 1 0 ---T--T-----T--T-T-T-T-T--T--T---T---T--T-----¬ |B |C3| ST |C2|C1|C0|ES|XX|PE|UE |OE|ZE|DE|IE| L--+--+-----+--+--+--+--+--+--+---+--+--+--+---
В обоих форматах биты 0...5 - флажки особых случаев. Они устанавливаются всегда при возникновении особых случаев, даже замаскированных установкой в 1 соответствующих битов регистра управления.
Приведем таблицу флажков особых случаев:
IE |
недействительная операция; |
DE |
денормализованный результат; |
ZE |
деление на нуль; |
OE |
переполнение; |
UE |
антипереполнение; |
PE |
неточный результат. |
Для того, чтобы сбросить установившийся флажок, программа должна явным образом установить его в нуль, выполнив команду записи в регистр состояния.
Назначение бита 7 регистра состояния различно для сопроцессора 8087 и сопроцессоров 80287/80387.
Для сопроцессора 8087 этот бит обозначается IR и содержит флаг запроса прерывания при возникновении незамаскированного особого случая. В этом случае флаг устанавливается в 1.
Сопроцессоры 80287/80387 используют бит 7 в качестве флага суммарной ошибки, который устанавливается в 1 при возникновении незамаскированного особого случая.
Биты C0, C1, C2, C3 - это коды условий. Они определяются по результату выполнения команд сравнения и команды нахождения остатка. Мы расскажем о них при описании соответствующих команд сопроцессора.
Поле ST занимает три бита 11...13 и содержит номер численного регистра, являющегося вершиной стека численных регистров.
Бит B - бит занятости. Он устанавливается в 1, когда процессор выполняет команду или когда происходит прерывание от сопроцессора. Если сопроцессор свободен, бит занятости установлен в 0.
Регистры указателя команды и указателя операнда предназначены для обработки особых случаев, возникающих при выполнении команд в сопроцессоре.
В сопроцессоре 8087 указатель команды содержит 20-разрядный адрес команды, вызвавшей особый случай и код выполняемой в этот момент операции. Адрес команды здесь указывается без учета предшествующих команде префиксов:
---------------------------------------------------¬ |Адрес команды (0...15) | +------------------------T-T-----------------------+ |Адрес команды (16...19) |X| Код операции (0...10) | L------------------------+-+------------------------
Сопроцессоры 80287/80387 в реальном режиме работы имеют такой же формат регистра указателя команд, однако этот указатель показывает на первый префикс команды, вызвавшей особый случай.
Защищенный режим работы центрального процессора и сопроцессора выходит за рамки данной книги, однако для полноты изложения приведем формат указателей и для этого режима. В защищенном режиме адрес состоит из селектора (в какой-то степени соответствует сегментной компоненте адреса реального режима) и смещения. Формат указателя команды для защищенного режима представлен на следующем рисунке:
---------------------------------------------------¬ | Смещение команды | +--------------------------------------------------+ | Селектор команды | L---------------------------------------------------
Код операции здесь отсутствует, но его легко получить, пользуясь адресом команды.
Если при возникновении особого случая использовался операнд, находящийся в оперативной памяти, его адрес записывается в регистр указателя операнда. Приведем форматы этого регистра для реального и защищенного режимов работы.
Формат указателя операнда для реального режима:
---------------------------------------------------¬ | Адрес операнда (0...15) | +-------------------------------T------------------+ | Адрес операнда (16...19) |XXXXXXXXXXXXXXXXXX| L-------------------------------+-------------------
Формат указателя операнда для защищенного
режима:
---------------------------------------------------¬ | Смещение операнда | +--------------------------------------------------+ | Селектор операнда | L---------------------------------------------------
Возможны три формата команд сопроцессора, аналогичные форматам команд центральных процессоров 8086/80286/80386. Это команды с обращением к оперативной памяти, команды с обращением к одному из численных регистров и команды без операндов, заданных явным образом.
Команды с обращением к памяти могут занимать от двух до четырех байтов, в зависимости от способа адресации операнда, находящегося в памяти:
|1 байт |1 байт |1 байт |1 байт | +-----T----+---T----T---+-----------+-----------+ |11011|КОП1|MOD|КОП2|R/M| Смещение1 | Смещение2 | L-----+----+---+----+---+-----------+------------
Первые пять битов соответствуют команде центрального процессора ESC. Поля КОП1 и КОП2 определяют выполняемую команду, т.е. содержат код операции. Поля MOD и R/M вместе с полями "Смещение1" и "Смещение2" задают адрес операнда в памяти аналогично тому, как это происходит в процессорах 8086/80286/80386. Однако есть и отличия, связанные с возможностью адресации численных регистров сопроцессора.
Приведем таблицу, показывающую зависимость способа адресации от содержимого полей MOD и R/M:
-----T----------------------------------------------¬ |Поле| Поле MOD | |R/M +---------T---------------T----------------T---+ | | 00 | 01 | 10 |11 | +----+---------+---------------+----------------+---+ |000 |(bx)+(si)|(bx)+(si)+disp8|(bx)+(si)+disp16|ST0| |001 |(bx)+(di)|(bx)+(di)+disp8|(bx)+(di)+disp16|ST1| |010 |(bp)+(si)|(bp)+(si)+disp8|(bp)+(si)+disp16|ST2| |011 |(bp)+(di)|(bp)+(di)+disp8|(bp)+(di)+disp16|ST3| |100 | (si) | (si)+disp8 | (si)+disp16 |ST4| |101 | (di) | (di)+disp8 | (di)+disp16 |ST5| |110 | disp16 | (bp)+disp8 | (bp)+disp16 |ST6| |111 | (bx) | (bx)+disp8 | (bx)+disp16 |ST7| L----+---------+---------------+----------------+----
Если в таблице указаны значения смещения disp8 или disp16, это означает, что в команде присуствует один или два байта смещения, соответственно.
Если поле MOD содержит значение 11, возможна адресация численных регистров ST0...ST1. При этом команда не содержит байтов смещения.
Формат команды с обращением к численному регистру приведен на следующем рисунке:
|1 байт |1 байт | +-----T----+---T----T---+ |11011|КОП1|11 |КОП2|STi| L-----+----+---+----+----
Видно, что это есть частный случай предыдущей команды, в которой поле MOD содержит значение 11 и отсутствуют байты смещения.
Самый простой формат имеют команды без явного обращения к операндам:
|1 байт |1 байт | +-----T----+---T--------+ |11011|КОП1|11 | КОП2 | L-----+----+---+---------
Разумеется, если вы пишете программу для сопроцессора на языке ассемблера, вы можете использовать мнемоническое обозначение команд. Все мнемоники команд сопроцесора начинаются с буквы F, поэтому их легко отличить от команд процессоров 8086/80286/80386/80486.
Все команды сопроцессора можно разделить на несколько групп:
Команды пересылки данных предназначены для загрузки чисел из оперативной памяти в численные регитры, записи данных из численных регистров в операивную память, копирования данных из одного численного регистра в другой.
Арифметические команды выполняют такие операции, как сложение, вычитание, умножение, деление, извлечение квадратного корня, нахождение частичного остатка, округление и т.п.
Команды сравнения сравнивают вещественные и целые числа, выполняют анализ чисел.
Трансцендентные команды предназначены для вычисления различных тригонометрических, логорифмических, показательных и гиперболических функций - sin(), cos(), tg() и т.п.
Последняя группа команд - управляющие команды - обеспечивают установку режима работы арифметического сопроцессора, его сброс и инициализацию, перевод сопроцессора в защищенный режим работы и т.д.
Следующие разделы будут посвящены детальному описанию различных групп команд сопроцессора.
FLD ST(0) <- память, вещественный формат FILD ST(0) <- память, целый формат FBLD ST(0) <- память, десятичный формат
Команды FLD, FILD, FBLD загружают в вершину стека вещественное, целое и десятичное числа, соответственно.
При выполнении этих команд операнд считывается из оперативной памяти, преобразуется в формат с расширенной точностью. Затем поле ST регистра состояния уменьшается на единицу и выполняется запись операнда в численный регистр, определяемый новым значением поля ST. Т.е. операнд записывается в стек численных регистров, а указатель стека - поле ST - уменьшается на единицу. По своему действию эти команды напоминают команду PUSH центрального процессора.
Непосредственно перед загрузкой численного регистра проверяется содержимое поля TAG0. Если это содержимое не равно 11 (пустой регистр), в регистре состояния устанавливается флаг IE (недействительная операция) и вырабатывается прерывание (если в регистре управления не установлена маска IM - маска недействительной операции).
FSTP память -> ST(0), вещественный формат FISTP память -> ST(0), целый формат FBSTP память -> ST(0), десятичный формат
Команды извлечения чисел из стека выполняют действие, обратное только что описанному. Содержимое численного регистра, номер которого определяется полем ST регистра состояния, преобразуется в необходимый формат и записывается в ячейки оперативной памяти, заданные операндом команды.
После записи содержимое поля ST увеличивается на единицу. Эти действия аналогичны выполняемым командой POP центрального процессора.
В зависимости от команды (FSTP, FISTP или FBSTP) производится преобразование формата (из расширенного в вещественный, целый или десятичный, соответственно). В процессе преобразования для команд FSTP и FISTP выполняется округление в соответствии с содержимым поля RC регистра управления. Для команды FBSTP округление всегда выполняется следующим образом - прибавляется число 0.5, затем дробная часть результата отбрасывается.
FST память -> ST(0), вещественный формат FIST память -> ST(0), целый формат FBST память -> ST(0), десятичный формат (только 80387, 80486)
Эти команды пересылают данные из верхушки стека в область памяти, указанную операндом команды. При этом содержимое указателя стека (поля ST) не изменяется.
Команда FST в качестве операнда может использовать ссылку на численный регистр ST(i), поэтому вы можете использовать эту команду для копирования верхушки стека в любой другой численный регистр.
При записи данных в оперативную память выполняется преобразование формата (в вещественный для FST, в целый для FIST и в десятичный для FBST.
Для сопроцессора 80286 вместо отсутствующей команды FBST можно выполнить следующие две команды, которые приведут к такому же результату:
FLD ST(0) FBSTP dec_number
FXCH ST(i) -> ST(0), ST(0) -> ST(i)
Команда выполняет обмен содержимым верхушки стека ST(0) и численного регистра, указанного в качестве операнда команды.
FLDZ 0 -> ST(0) - Загрузить нуль FLD1 1 -> ST(0) - Загрузить единицу FLDPI "Пи" -> ST(0) - Загрузить число "пи". FLDLG2 log102 -> ST(0) - Загрузить log102 FLDLN2 loge2 -> ST(0) - Загрузить loge2 FLDL2T loge10 -> ST(0) - Загрузить loge10 FLDL2E log2e -> ST(0) - Загрузить log2e
Гораздо быстрее загружать константы с помощью специальных команд, чем использовать команды загрузки данных из оперативной памяти.
Сопроцессор использует шесть основных типов
арифметических команд:
Fxxx |
Первый операнд берется из верхушки
стека (источник), второй - следующий элемент
стека. Результат выполнения команды
записывается в стек. |
Fxxx память |
Источник берется из памяти, приемником
является верхушка стека ST(0). Указатель стека ST не
изменяется, команда действительна только для
операндов с одинарной и двойной точностью. |
Fixxx память |
Аналогично предыдущему типу команды, но
операндами могут быть 16- или32-битовые целые
числа. |
Fxxx ST, ST(i) |
Для этого типа регистр ST(i) является
источником, а ST(0) - верхушка стека - приемником.
Указатель стека не изменяется. |
Fxxx ST(i), ST |
Для этого типа регитр ST(0) является
источником, а ST(i) - приемником. Указатель стека не
изменяется. |
FxxxP ST(i), ST |
Регистр ST(i) - приемник, регистр ST(0) -
источник. После выполнения команды источник ST(0)
извлекается из стека. |
Строка "xxx" может принимать следующие значения:
ADD Сложение SUB Вычитание SUBR Обратное вычитание, уменьшаемое и вычитаемое меняются местами MUL Умножение DIV Деление DIVR Обратное деление, делимое и делитель меняются местами
Кроме основных арифметических команд имеются
дополнительные арифметические команды:
FSQRT |
Извлечение квадратного корня |
FSCALE |
Масштабирование на степень числа 2 |
FPREM |
Вычисление частичного остатка |
FRNDINT |
Округление до целого |
FXTRACT |
Выделение порядка числа и мантиссы |
FABS |
Вычисление абсолютной величины числа |
FCHS |
Изменение знака числа |
По команде FSQRT вычисленное значение квадратного корня записывается в верхушку стека ST(0).
Команда FSCALE изменяет порядок числа, находящегося в ST(0). По этой команде значение порядка числа ST(0) складывается с масштабным коэффициентом, который должен быть предварительно записан в ST(1). Действие этой команды можно представить следующей формулой:
ST(0) = ST(0) * 2n, где -215 <= n <= +215
В этой формуле n - это ST(1).
Команда FPREM вычисляет остаток от деления делимого ST(0) на делитель ST(1). Знак результата равен знаку ST(0), а сам результат получается в вершине стека ST(0).
Действие команды заключается в сдвигах и вычитания, аналогично "ручному" делению "в столбик". После выполнения команды флаг C2 регистра состояния может принимать следующие значения:
0 |
остаток от деления, полученный в ST(0),
меньше делителя ST(1), команда завершилась
полностью; |
1 |
ST(0) содержит частичный остаток,
программа должна еще раз выполнить команду для
получения точного значения остатка. |
Команда RNDINT округляет ST(0) в соответствии с содержимым поля RC управляющего регистра.
Команда FABS вычисляет абсолютное значение ST(0). Аналогично, команда FCHS изменяет знак ST(0) на противоположный.
В процессорах 8086/80286/80386 команды условных
переходов выполняются в соответствии с
установкой отдельных битов регистра флагов
процессора. В арифметическом сопроцессоре
существуют специальные команды сравнений, по
результатам выполнения которых устанавливаются
биты кодов условий в регистре состояния:
FCOM |
Сравнение |
FICOM |
Целочисленное сравнение |
FCOMP |
Сравнение и извлечение из стека |
FICOMP |
Целочисленное сравнение и извлечение из
стека |
FCOMPP |
Сравнение и двойное извлечение из стека |
FTST |
Сравнение операнда с нулем |
FXAM |
Анализ операнда |
Команда FCOM вычитает содержимое операнда, размещенного в оперативной памяти, из верхушки стека ST(0). Результат вычитания никуда не записывается и указатель верхушки стека ST не изменяется.
Обозначим операнд команды сравнения как
"x". В следующей таблице приведем значения
битов кодов условия после выполнения команды
"FCOM x":
C3 |
C0 |
Условие |
0 |
0 |
ST(0) > x |
0 |
1 |
ST(0) < x |
1 |
0 |
ST(0) = x |
1 |
1 |
ST(0) и x не сравнимы. |
Последняя комбинация возникает при попытке сравнения нечисел, неопределенностей или бесконечностей, а также в некоторых других случаях.
Команда FICOM работает с 16- или 32-битовыми числами, в остальном она аналогична команде FCOM.
Команды FCOMP и FICOMP аналогичны, соответственно, командам FCOM и FICOM, за исключением того, что после выполнения операнд извлекается из стека.
Команда FCOMPP выполняет те же действия, что и FCOM, но она после выполнения извлекает из стека оба операнда, участвовавших в сравнении.
Для сравнения операнда с нулем предназначена
команда FTST. После ее выполнения коды условий
устанавливаются в соответствии со следующей
таблицей:
C3 |
C0 |
Условие |
0 |
0 |
ST(0) > 0 |
0 |
1 |
ST(0) < 0 |
1 |
0 |
ST(0) = 0 |
1 |
1 |
ST(0) и 0 не сравнимы. |
Команда FXAM анализирует содержимое ST(0). После ее выполнения устанавливаются коды условий, по которым можно судить о знаке числа, о его конечности или бесконечности, нормализованности и т.д.
Бит C1 содержит знак анализируемого числа:
C1 |
Знак числа |
0 |
положительное число; |
1 |
отрицательное число. |
С помощью бита C0 можно определить, является
число конечным или бесконечным:
C0 |
Конечность/бесконечность числа |
0 |
конечное число; |
1 |
бесконечное число. |
Для конечных чисел дальнейшая классификация
может проводиться по содержимому кодов условий C2
и C3:
C3 |
C2 |
Описание числа |
0 |
0 |
Ненормализованное число |
0 |
1 |
Нормализованное число |
1 |
0 |
Нулевое число |
1 |
1 |
Число денормализовано |
Аналогично, для бесконечных чисел коды условий
C2 и C3 имеют следующее значение:
C3 |
C2 |
Описание числа |
0 |
0 |
Нечисло |
0 |
1 |
Бесконечное число |
1 |
0 |
Пустое число |
1 |
1 |
Пустое число |
С помощью команды "FSTSW AX" программа может
переписать содержимое регистра состояния
сопроцессора в регистр AX центрального
процессора. Далее содержимое регистра AH можно
переписать в регистр флагов центрального
процессора при помощи команды SAHF. Биты кодов
условий сопроцессора отображаются на регистр
флагов центрального процессора таким образом,
что для анализа кодов условий можно использовать
команды условных переходов:
---T--T--T--T--T--T--T--¬ |B |C3| |ST| |C2|C1|C0| L--+--+--+--+--+--+--+--- ---T--T--T--T--T--T--T--¬ |SF|ZF| |AF| |PF| |CF| L--+--+--+--+--+--+--+---
Например, в следующем фрагменте программы выполняется переход к метке error, если операнды несравнимы:
fcom fstsw ax sahf je error
Трансцендентные команды предназначены для вычисления таких функций, как тригонометрические (sin, cos, tg,...), обратные тригонометрические (arcsin, arccos,...), показательные (xy, 2x, 10x, ex), гиперболические (sh, ch, th,...), обратные гиперболические (arsh, arch, arcth,...).
В следующей таблице приведены все
трансцендентные команды сопроцессора:
FPTAN |
Вычисление частичного тангенса |
FPATAN |
Вычисление частичного арктангенса |
FYL2X |
Вычисление y*log2(x) |
FYL2XP1 |
Вычисление y*log2(x+1) |
F2XM1 |
Вычисление 2x-1 |
FCOS |
Вычисление cos(x) (только 80387/80486) |
FSIN |
Вычисление sin(x) (только 80387/80486) |
FSINCOS |
Вычисление sin(x) и cos(x) одновременно
(только 80387/80486) |
Команда FPTAN вычисляет частичный тангенс ST(0), размещая в стеке такие два числа x и y, что y/x = tg(ST(0)).
После выполнения команды число y располагается в ST(0), а число x включается в стек сверху (т.е. записывается в ST(1)). Аргумент команды FPTAN должен находится в пределах:
0 <= ST(0) <= pi/4
Пользуясь полученным значением частичного тангенса, можно вычислить другие тригонометрические функции по следующим формулам:
sin(z) = 2*(y/x) / (1 + (y/x)2) cos(z) = (1 - (y/x)2) / (1 + (y/x)2) tg(z/2) = y/x; ctg(z/2) = x/y; cosec(z) = (1 + (y/x)2) / 2*(y/x) sec(z) = (1 + (y/x)2) / (1 - (y/x)2)
В этой таблице z - значение, находившееся в ST(0) до выполнения команды FPTAN, x и y - значения в регистрах ST(0) и ST(1), соответственно.
Команда FPATAN вычисляет частичный арктангенс z=arctg(ST(0)/ST(1))=arctg(x/y).
Перед выполнением команды числа x и y располагаются в ST(0) и ST(1), сответственно. Аргументы команды FPATAN должен находится в пределах:
0 < y < x
Результат записывается в ST(0).
Команда FYL2X вычисляет выражение y*log2(x), операнды x и y размещаются, соответственно, в ST(0) и ST(1). Операнды извлекаются из стека, а результат записывается в стек. параметр x должен быть положительным числом.
Пользуясь результатом выполнения этой команды, можно вычислить следующим образом логарифмические функции:
log2(x) = FYL2(x) loge(x) = loge(2) * log2(x) = FYL2X(loge(2), x) = = FYL2X(FLDLN2, x) log2(x) = log10(2) * log2(x) = FYL2X (log10(2), x) = = FYL2X(FLDLG2, x)
Функция FYL2XP1 вычисляет выражение y*log2(x+1), где x соответствует ST(0), а y - ST(1). Результат записывается в ST(0), оба операнда выталкиваются из стека и теряются.
На операнд x накладывается ограничение:
0 < x < 1 - 1/sqrt(2)
Команда F2XM1 вычисляет выражение 2x-1, где x - ST(0). Результат записывается в ST(0), параметр должен находится в следующих пределах:
0 <= x <= 0,5
Команда FCOS вычисляет cos(x) (только для 80387/80486). Параметр x должен находится в ST(0), туда же записывается результат выполнения команды.
Команда FSIN аналогична команде FCOS, но вычисляет значение косинуса ST(0).
Команда FSINCOS вычисляет одновременно значения синуса и косинуса параметра ST(0). Значение синуса записывается в ST(1), косинуса - в ST(0).
На этом мы закончим описание трансцендентных команд сопроцессора и перейдем к управляющим командам.
Управляющие команды предназначены для работы с нечисловыми регистрами сопроцессора. Некоторые команды имеют альтернативные варианты. Мнемоники этих команд могут начинаться с FN или с F. Первый вариант соответствует командам "без ожидания". Для команд "без ожидания" процессор не проверяет, занят ли сопроцессор выполнением команды, т.е. бит занятости B не проверяется. Численные особые случаи также игнорируются.
Варианты команд "с ожиданием" действуют также, как и обычные команды сопроцессора.
Приведем таблицу управляющих команд сопроцессора:
FNSTCW (FSTCW) Записать управляющее слово FLDCW Загрузить управляющее слово FNSTSW (FSTSW) Записать слово состояния FNSTSW AX (FSTSW AX) Записать слово состояния в AX, не поддерживается сопроцессором 8087 FNCLEX (FCLEX) Сбросить особые случаи FNINIT (FINIT) Инициализировать сопроцессор FNSTENV (FSTENV) Записать среду FLDENV Загрузить среду FNSAVE (FSAVE) Записать полное состояние FRSTOR Восстановить полное состояние FINCSTP Увеличить указатель стека на 1 FDECSTP Уменьшить указатель стека на 1 FFREE Освободить регистр FNOP Холостая команда, нет операции FSETPM Установить защищенный режим работы
Команда FNSTCW записывает содержимое управляющего регистра в оперативную память.
Команда FLDCW загружает управляющий регистр данными из оперативной памяти и обычно используется для изменения режима работы сопроцессора.
Команда FNSTSW записывает содержимое регистра состояния в оперативную память. Команда FNSTSW AX записывает содержимое этого регистра в регистр AX центрального процессора для его последующего анализа командами условных переходов.
Сопроцессор 8087 не имеет варианта команды FSTSW AX, поэтому приходится вначале записывать регистр состояния в память, а затем в регистр флагов процессора 8086.
Команда FNCLEX сбрасывает флаги особых случаев в регистре состояния сопроцессора. Кроме того, сбрасываются биты ES и B.
Команда FNINIT инициализирует регистр состояния, управляющий регистр и регистр тегов в соответствии со следующей таблицей:
Регистр Устанавливаемый режим работы Управляющий Проективная бесконечность, округление к ближайшему, расширенная точность, все особые случаи замаскированы. Состояния B=0 (бит занятости сброшен), код условия не определен, ST=ES=0, флаги особых случаев установлены в нуль. Тегов Все поля регистра тегов содержат значение 11 (пустой регистр).
Команда FNSTENV записывает в память содержимое
всех регистров, кроме численных, в следующем
формате:
------------------------¬ | Управляющий регистр | +-----------------------+ | Регистр состояния | +-----------------------+ | Регистр тегов | +-----------------------+ | | +- Указатель команды -+ | | +-----------------------+ | | +- Указатель операнда -+ | | L------------------------
Команда FLDENV предназначена для загрузки регистров, сохраненных ранее командой FNSTENV. Обе эти команды полезны в программах обработки особых случаев.
Команды FNSAVE и FRSTOR действуют аналогично командам FNSTENV и FLDENV, но они дополнительно сохраняют и восстанавливают содержимое численных регистров. Формат области сохранения регистров, занимающей 94 байта, приведен на следующем рисунке:
------------------------¬ | Управляющий регистр | +-----------------------+ | Регистр состояния | +-----------------------+ | Регистр тегов | +-----------------------+ | | +- Указатель команды -+ | | +-----------------------+ | | +- Указатель операнда -+ | | +-----------------------+-------------------------------¬ | ST(0) | +-------------------------------------------------------+ | ST(1) | +-------------------------------------------------------+ | ST(2) | +-------------------------------------------------------+ | ST(3) | +-------------------------------------------------------+ | ST(4) | +-------------------------------------------------------+ | ST(5) | +-------------------------------------------------------+ | ST(6) | +-------------------------------------------------------+ | ST(7) | L--------------------------------------------------------
Команды FINCSTP и FDECSTP, соответственно, увеличивают и уменьшают на 1 указатель стека SP.
Команда FFREE ST(i) помечает численный регистр ST(i) как пустой, записывая в соответствующее поле регистра тегов значение 11.
Команда FNOP не производит никаких действий.
Команда FSETPM переводит сопроцессор в защищенный режим работы. Подробное рассмотрение защищенного режима работы выходит за рамки данной книги.
Используя языки высокого уровня, такие как Си или Паскаль, вы можете даже и не знать, что созданная вами программа использует для вычислений арифметический сопроцессор. При установке системы программирования QuickC или C 6.0 вам предоставляется возможность выброа одного из трех вариантов стандартной библиотеки:
Первый вариант (библиотека эмулятора) используется по умолчанию. Программы, которые создаются с использованием эмулятора, будут работать как при наличии в системе сопроцессора, так и при его отсуствии. В последнем случае вычисления с плавающей точкой выполняются специальными подпрограмами, которые присоединяются к вашей программе на этапе редактирования. Ваша программа сама определит факт наличия (или отсуствия) сопроцессора и выберет соответствующий способ выполнения вычислений - либо с использованием сопроцесора, либо с использованием подпрограмм эмуляции сопроцессора.
Все что вам нужно для работы с библиотекой эмуляции - это просто выбрать ее при установке системы программирования. Это самый простой способ программирования сопроцессора, когда вам, вообще говоря, совсем не надо его программировать - всю работу по использоанию сопроцессора выполнят модули библиотеки эмуляции.
Второй вариант библиотеки рассчитан на наличие сопроцессора. Если сопроцессора нет, программа работать не будет. Но если известно, что сопроцессор есть (например, процессор 80486 всегда содержит блок арифметики), то вам имеет смысл использовать именно этот вариант как самый быстродействующий.
Третий вариант не использует сопроцессор совсем. Все вычисления выполняются специальными подпрограммами, входящими в состав библиотеки альтернативной математики и подключающимися к вашей программе автоматически на этапе редактирования.
К сожалению, есть программы, в которых использование библиотеки эмуляции невозможно или крайне затруднительно:
В случае с резидентными программами невозможность использования библиотеки эмулятора вызвана тем, что после оставления программы резидентной в памяти, например, функцией _dos_keep(), она теряет доступ к модулям эмуляции. Механизм вызова программ эмуляции основан на использовании прерываний с номерами 34h...3Eh. Перед тем как оставить программу резидентной, функция _dos_keep() восстанавливает содержимое этих векторов, делая невозможным доступ резидентной программе к модулям эмулятора. Да и самих этих модулей уже нет в памяти - на их место может быть загружена новая программа.
Поэтому руководство по Си рекомендует для резидентных программ использовать библиотеку альтернативной математики. Но эта библиотека, увы, не использует сопроцессор.
Ситуация с драйверами аналогична - драйверы, как правило, составляются на языке ассемблера, поэтому средства эмуляции библиотек Си недоступны.
Выходом может быть непосредственное программирование сопроцессора на языке ассемблера. При этом вы можете полностью использовать все возможности сопроцессора и добиться от программы наибольшей эффективности вычислений.
Какие средства можно использовать для составления программ для сопроцессора?
Обычно это или ассемблер MASM (возможно использование TASM), либо интегрированная среда разработки QuickC версии 2.01, содержащая встроенный Quick Assembler.
Приведем пример самой простой программы, подготовленный для трансляции программой Quick Assemler. Эта программа выполняет вычисления по следующей несложной формуле:
z = x + y;
Значения x и y задаются в виде констант:
.MODEL TINY .STACK 100h .DATA ; Здесь находятся константы с одинарной ; точностью x и y x dd 1.0 y dd 2.0 ; Резервируем четыре байта для результата z dd ? .CODE .STARTUP ; Записываем в стек численных регистров ; значение x fld x ; Складываем содержимое верхушки стека ; с константой y fadd y ; Записываем результат в ячейку z fstp z ; Завершаем работу программы и ; возвращаем управление операционной системе .EXIT 0 END
Как убедиться в том, что программа работает правильно?
Для этого мы используем отладчик CodeView, содержащий очень удобные средства отладки программ, работающих с арифметическим сопроцессором.
Запустим отладчик CodeView, передав ему в качестве параметра имя приведенной выше программы:
cv test87.com
После того, как отладчик запустится, откройте окно регистров сопроцессора, нажав комбинацию клавиш Alt-V-7:
После этого на в нижней части экрана появится окно регистров сопроцессора:
Пусть вас не смущает то, что в этом окне пока не показывается состояние регистров сопроцессора. Нажмите клавишу F8, выполнив один шаг программы. Окно сопроцессора будет содержать следующую информацию:
Теперь вы видите содержимое регистров управления и состояния (cControl, cStatus), регистра тегов (cTag), регистров указателей команд и данных (Instr Ptr, Data Ptr), код выполняемой команды (Opcode). Отображается также содержимое стека численных регистров (Stack), но пока это поле пустое, так как все численные регистры отмечены в регистре тегов как пустые (код 11).
Нажмите еще раз клавишу F8, выполнив следующую команду программы. Эта команда запишет в стек численных регистров значение переменной x:
Теперь в области регистров стека показано содержимое регистра cST(0), причем как в двоичном виде, так и с использованием экспоненциальной (научной) нотации.
Как и следовало ожидать, регистр ST(0) содержит величину 1.0.
Выполним еще одну команду, прибавляющую к содержимому ST(0) значение 2.0 из переменной y. Теперь регистр ST(0) содержит величину 3.0:
Последняя команда выталкивает из стека хранящееся там значение (3.0) и записывает его в переменную z. Теперь стек численных регистров снова пуст:
Отладчик CodeView обладает мощными средствами динамического просмотра состояния сопроцессора. Однако этот отладчик невозможно использовать для отладки драйверов. Мы уже говорили вам о проблемах, возникающих при отладке драйверов, в первом томе "Библиотеки системного программиста".
Там же нами была предложена методика отладки драйверов, основанная на включении в исходный текст драйвера подпрограмм, выводящих на экран содержимое регистров центрального процессора или областей памяти. Мы привели исходный текст подпрограммы ntrace, которая выводит на экран содержимое всех регистров центрального процессора.
Если ваш драйвер использует сопроцессор, вам, вероятно, потребуется также содержимое регистров сопроцессора. Приведем текст подпрограммы ntrace87, которая наряду с содержимым регистров центрального процессора, выводит содержимое регистров арифметического сопроцессора:
include sysp.inc .MODEL tiny .CODE PUBLIC ntrace87 ;========================================== ; Процедура выводит на экран содержимое ; всех регистров центрального процессора ; и сопроцессора. Затем она ожидает нажатия на ; любую клавишу. ; После возвращения из процедуры ; все регистры восстанавливаются, в том ; числе регистры сопроцессора. ntrace87 proc near ; Сохраняем в стеке регистры, ; содержимое которых будет изменяться pushf push ax push bx push cx push dx push ds push bp mov bp,sp push cs pop ds ; Сохраняем полное состояние сопроцессора fsave cs:regs_87 ; Выводим сообщение об останове mov dx,offset cs:trace_msg @@out_str ; Выводим содержимое всех регистров mov ax,cs ; cs call Print_word @@out_ch ':' mov ax,[bp]+14 ; ip call Print_word @@out_ch 13,10,13,10,'A','X','=' mov ax,[bp]+10 call Print_word @@out_ch ' ','B','X','=' mov ax,[bp]+8 call Print_word @@out_ch ' ','C','X','=' mov ax,[bp]+6 call Print_word @@out_ch ' ','D','X','=' mov ax,[bp]+4 call Print_word @@out_ch ' ','S','P','=' mov ax,bp add ax,16 call Print_word @@out_ch ' ','B','P','=' mov ax,[bp] call Print_word @@out_ch ' ','S','I','=' mov ax,si call Print_word @@out_ch ' ','D','I','=' mov ax,di call Print_word @@out_ch 13,10,'D','S','=' mov ax,[bp]+2 call Print_word @@out_ch ' ','E','S','=' mov ax,es call Print_word @@out_ch ' ','S','S','=' mov ax,ss call Print_word @@out_ch ' ','F','=' mov ax,[bp]+12 call Print_word ; Выводим содержимое регистров сопроцессора lea dx,cs:r87_msg @@out_str ; Выводим содержимое управляющего регистра @@out_ch 'C','N','T','R','=' mov ax, cs:regs_87.cr call Print_word ; Выводим содержимое регистра состояния @@out_ch ' ','S','T','A','T','E','=' mov ax, cs:regs_87.sr call Print_word ; Выводим содержимое регситра тегов @@out_ch ' ','T','A','G','=' mov ax, cs:regs_87.tg call Print_word ; Выводим содержимое указателя адреса @@out_ch ' ','C','M','D','A','D','R','=' mov ax, cs:regs_87.cmdhi and ah, 0f0h mov al, ah mov cl, 4 ror al, cl call Print_byte mov ax, cs:regs_87.cmdlo call Print_word @@out_ch ' ' ; Выводим содержимое указателя операнда @@out_ch ' ','O','P','R','A','D','R','=' mov ax, cs:regs_87.oprhi and ah, 0f0h mov al, ah mov cl, 4 ror al, cl call Print_byte mov ax, cs:regs_87.oprlo call Print_word ; Выводим содержимое непустых численных регистров lea dx,cs:nr_msg @@out_str mov cx, 8 ; количество регистров - 8 mov dx, 0 ; индекс текущего регистра mov bx, cs:regs_87.tg ; содержимое регистра тегов ; Цикл по стеку численных регистров nreg_loop: ; Проверяем поле регистра тегов, соответствующее ; текущему обрабатываемому численному регистру mov ax, bx and ax, 0c000h cmp ax, 0c000h ; Если это поле равно 11B, считаем, что данный ; численный регистр пуст, переходим к следующему je continue ; Выводим на экран содержимое численного регистра call Print_numreg continue: ; Сдвигаем содержимое регистра тегов для ; обработки поля, соответствующего следующему ; регистру. rol bx, 1 rol bx, 1 inc dx ; увеличиваем индекс текущего регистра loop nreg_loop lea dx,cs:hit_msg @@out_str ; Ожидаем нажатия на любую клавишу mov ax,0 int 16h ; Восстанавливаем содержимое регистров frstor cs:regs_87 pop bp pop ds pop dx pop cx pop bx pop ax popf ret trace_msg db 13,10,'>---- BREAK ----> At address ','$' hit_msg db 13,10,'Hit any key...','$' r87_msg db 13,10,13,10,'Coprocessor state:',13,10,'$' nr_msg db 13,10,'Numeric Registers:',13,10,'$' regs_87 db 94 dup(?) ten db 10 ntrace87 endp ;========================================== ; Процедура выводит на экран содержимое ; численного регистра с номером, заданным ; в регистре al Print_numreg proc near push cx push bx ; Выводим обозначение численного регистра push dx @@out_ch 'S','T','(' pop dx mov al, dl call Print_byte push dx @@out_ch ')','=' pop dx ; Выводим содержимое численного регистра в ; шестнадцатеричном формате mov cx, 10 ; счетчик байтов в числе с ; расширенной точностью mov bp, 10 ; первоначальное смещение ; к старшему байту числа ; Смещение к полю первого численного регистра ; в области сохранения mov bx, offset cs:regs_87.st0 ; Вычисляем смещение старшего байта численного ; регистра, номер которого задан в регистре DX mov ax, dx imul cs:ten add bx, ax dec bx ; Выводим в цикле 10 байтов числа pr_lp: push bx add bx, bp mov al, cs:[bx] call Print_byte pop bx dec bp loop pr_lp push dx @@out_ch 13,10 pop dx pop bx pop cx ret Print_numreg endp ;========================================== ; Процедура выводит на экран содержимое AL Print_byte proc near push ax push bx push dx call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl pop dx pop bx pop ax ret Print_byte endp ;========================================== ; Процедура выводит на экран содержимое AX Print_word proc near push ax push bx push dx push ax mov cl,8 rol ax,cl call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl pop ax call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl pop dx pop bx pop ax ret Print_word endp Byte_to_hex proc near ;-------------------- ; al - input byte ; dx - output hex ;-------------------- push ds push cx push bx lea bx,tabl mov dx,cs mov ds,dx push ax and al,0fh xlat mov dl,al pop ax mov cl,4 shr al,cl xlat mov dh,al pop bx pop cx pop ds ret tabl db '0123456789ABCDEF' Byte_to_hex endp end
Работа программы основана на использовании команды FSAVE, сохраняющей в памяти содержимое всех регистров сопроцессора. Область сохранения описывается следующей структурой, определенной в файле sysp.inc:
State87 struc cr dw ? sr dw ? tg dw ? cmdlo dw ? cmdhi dw ? oprlo dw ? oprhi dw ? st0 dt ? st1 dt ? st2 dt ? st3 dt ? st4 dt ? st5 dt ? st6 dt ? st7 dt ? State87 ends
Для демонстрации возможностей ntrace87 мы немного изменили нашу первую программу, работающую с сопроцессором - после каждой комадны сопроцессора вставили вызов ntrace87:
.MODEL tiny DOSSEG EXTRN ntrace87:NEAR .STACK 100h .DATA x dd 1.0 y dd 2.0 ; Резервируем четыре байта для результата z dd ? .CODE .STARTUP push cs pop ds ; Записываем в стек численных регистров ; значение x call ntrace87 fld x call ntrace87 ; Складываем содержимое верхушки стека ; с константой y fadd y call ntrace87 ; Записываем результат в ячейку z fstp z call ntrace87 ; Завершаем работу программы и ; возвращаем управление операционной системе quit: .EXIT 0 END
В процессе работы этой программы на каждом шаге на экран выводится дамп содержимого регистров центрального процессора и сопроцессора (пустые численные регистры не отображаются):
>---- BREAK ----> At address 2314:0105 AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE DS=2314 ES=2314 SS=2314 F=7202 Coprocessor state: CNTR=037F STATE=4000 TAG=FFFF CMDADR=023256 OPRADR=02365E Numeric Registers: Hit any key... >---- BREAK ----> At address 2314:010D AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE DS=2314 ES=2314 SS=2314 F=7202 Coprocessor state: CNTR=037F STATE=7800 TAG=3FFF CMDADR=023246 OPRADR=02365E Numeric Registers: ST(00)=3FFF8000000000000000 Hit any key... >---- BREAK ----> At address 2314:0115 AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE DS=2314 ES=2314 SS=2314 F=7202 Coprocessor state: CNTR=037F STATE=7800 TAG=3FFF CMDADR=02324E OPRADR=023662 Numeric Registers: ST(00)=4000C000000000000000 Hit any key... >---- BREAK ----> At address 2314:011D AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE DS=2314 ES=2314 SS=2314 F=7202 Coprocessor state: CNTR=037F STATE=4000 TAG=FFFF CMDADR=023256 OPRADR=023666 Numeric Registers: Hit any key...
В арифметическом сопроцессоре имеются два механизма обработки ошибок, возникающих при выполнении различных команд. Первый механизм основан на генерации так называемого прерывания особого случая (INT 10h). Это прерывание вырабатывается в том случае, когда происходит какая-нибудь ошибка (например, деление на нуль) при условии, что соответствующие биты масок особых случаев в регистре управления не установлены. При втором способе обработки ошибок все особые случаи маскируются (соответствующие биты управляющего регистра устанавливаются в единицу) и в случае ошибки сопроцессор в качестве результата возвращает некоторое заранее известное особое значение (нечисло, неопределенность или бесконечность).
Программист может выбирать между этими способами обработки ошибок, маскируя или разрешая прерывание по особому случаю. Если прерывание особого случая замаскировано, можно предложить следующий способ обнаружения ошибки:
Кроме того, после выполнения команды полезно проверить получившийся результат на принадлежность к множеству особых значений.
Рассмотрим возможные особые случаи сопроцессора в реальном режиме.
В результате выполнения некоторых операций может возникнуть такая ситуация, когда невозможно точно представить результат. Например, при результатом деления числа 1.0 на 3.0 является бесконечная периодическая двоичная дробь 0.010101... Такое число не может быть представлено точно ни в одном формате вещественных чисел.
Обычно неточный результат является результатом округления и может не рассматриваться как ошибка.
Если результат выполнения операции слишком велик и не может быть представлен в формате приемника результата, фиксируется особый случай переполнения.
Этот особый случай обязательно произойдет, например, при сложении максимального числа расширенной точности самим с собой или при преобразовании этого числа в формат с двойной или одинарной точностью.
Так как для хранения промежуточных результатов используется 80-битовое представление, при выполнении операций над числами с одинарной или двойной точностью переполнения, как правило, не происходит. Огромный диапазон чисел с расширенной точностью гарантирует правильность представления больших по абсолютной величине результатов операций с числами одинарной и двойной точности.
Антипереполнение возникает тогда, когда результат слишком мал для его представления в формате приемника результата операции, но все же отличен от нуля. Например, если делается попытка преобразовать наименьшее положительное число с расширенной точностью в формат числа с двойной или одинарной точностью.
Если вы используете числа только с двойной или одинарной точностью, а для хранения промежуточных результатов используете формат с расширенной точностью, особый случай антипереполнения, как правило, не возникает.
Этот особый случай возникает при попытке выполнить деление конечного ненулевого числа на нуль.
В афинном режиме при делении конечных (положительных или отрицательных) чисел на нуль (положительный или отрицательный) в качестве результата возвращается бесконечность. Знак этой бесконечности зависит от знака делимого и от знака нуля. Например, при делении положительного ненулевого числа на положительный нуль получается положительная бесконечность, при делении положительного ненулевого числа на отрицательный нуль - отрицательная бесконечность.
В проективном режиме, а также при попытке деления нуля на нуль возникает особый случай недействительной операции, который будет рассмотрен ниже.
Этот особый случай возникает при попытке выполнения таких запрещенных команд, как деление нуля на нуль, извлечения корня из отрицательного числа, обращение к несуществующему регистру сопроцессора или при попытке использования в качестве операндов команд нечисел, неопределенностей или бесконечности (для трансцендентных функций).
Мы уже говорили о том, что сопроцессор использует операнды в нормализованной форме. Однако при выполнении операции может оказаться, что результат слишком мал по абсолютной величине для представления его в нормализованной форме. Можно было бы считать такой результат нулевым, однако это привело бы к снижению точности вычислений или даже к грубым ошибкам. Например, вычисляется следующее выражение:
(y-x)+x;
Если разность (y-x) вызывает антипереполнение и в качестве результата берется нулевое значение, то после вычисления всего выражения получится x. Если же пойти на расширение диапазона представления чисел за счет снижения точности и сформировать результат вычисления разности (y-x) как денормализованное число, выражение будет вычислено правильно и в результате получится y.
Таким образом, иногда целесообразно
замаскировать особый случай денормализованного
операнда и использовать денормализованные
числа. Однако при попытке деления на
ненормализованное число или извлечения из него
квадратного корня фиксируется особый случай
недействительной операции.