Еще одно устройство, которое мы опишем в этом томе - арифметический сопроцессор фирмы Intel. В старых моделях компьютеров сопроцессор устанавливался на системной плате в отдельной панельке и подключался непосредственно к центральному процессору. Современные процессоры Pentium содержат встроенный арифметический сопроцессор.
Арифметический сопроцессор предназначен для выполнения операций над числами в формате с плавающей точкой (вещественные числа) и длинными целыми числами. Он значительно (в десятки раз) ускоряет вычисления, связанные с вещественными числами. Сопроцессор может вычислять такие функции, как синус, косинус, тангенс, логарифмы и так далее. Разумеется, что с помощью сопроцессора можно выполнять и простейшие арифметические операции сложения, вычитания, умножения и деления.
Основная область применения арифметического сопроцессора - научные расчеты и машинная графика. Некоторые пакеты САПР, например, Autocad, отказываются работать, если в машине отсутствует сопроцессор.
Сопроцессор запускается центральным процессором. После запуска он выполняет все вычисления самостоятельно и параллельно с работой центрального процессора. Если центральный процессор выдает очередную команду сопроцессору в момент времени, когда тот еще не закончил выполнение предыдущей команды, центральный процессор переводится в состояние ожидания. Если же сопроцессор ничем не занят, центральный процессор, выдав команду сопроцессору, продолжает свою работу, не дожидаясь завершения вычисления. Впрочем, есть специальные средства синхронизации (команда 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. Такое представление называется научной нотацией.
Арифметический сопроцессор может работать с вещественными числами в трех форматах:
Эти числа занимают в памяти, соответственно, 4, 8 или 10 байт (рис. 10.1).
Рис. 10.1. Различные представления вещественных чисел
В любом представлении старший бит определяет знак вещественного числа:
Все равные по абсолютному значению положительные и отрицательные числа отличаются только этим битом. В остальном числа с разным знаком полностью симметричны. Для представления отрицательных чисел здесь не используется дополнительный код, как это сделано в центральном процессоре.
Арифметический сопроцессор работает с нормализованными числами, поэтому поле мантиссы содержит мантиссу нормализованного числа.
Так как здесь используется двоичное представление чисел, сформулируем определение нормализованного числа для двоичного представления:
Так как для нормализованного двоичного числа целая часть всегда равна единице, то эту единицу можно не хранить. Именно так и поступили разработчики арифметического сопроцессора - в форматах одинарной и двойной точности целая часть мантиссы не хранится. Таким образом экономится один бит памяти.
Для наглядности представим мантиссу числа в следующей форме:
n.nnnnnnnnnn...n
Здесь символом n обозначается либо 0, либо 1. Нормализованные числа в самой левой позиции содержат 1, поэтому их можно изобразить еще и в таком виде:
1.nnnnnnnnnn...n
Представление с расширенной точностью используется сопроцессором для выполнения всех операций. И даже более - все операции с числами сопроцессор выполняет над числами только в формате с расширенной точностью. В этом формате хранится и "лишний" бит целой части нормализованного числа.
Основная причина использования для вычислений расширенной точности - предохранение программы от возможной потери точности вычислений, связанной с большими различиями в порядках чисел, участвующих в арифметических операциях.
Поле порядка - это степень числа 2, на которую умножается мантисса, плюс смещение, равное 127 для одинарной точности, 1023 - для двойной точности и 16383 - для расширенной точности.
Для того, чтобы определить абсолютное значение числа с плавающей точкой, можно воспользоваться следующими формулами:
Одинарная точность:
1.(цифры мантиссы)*2(P-127)
Двойная точность:
1.(цифры мантиссы)*2(P-1023)
Расширенная точность:
1.(цифры мантиссы)*2(P-16383)
Знак числа, как мы уже говорили, определяется старшим битом.
Приведем конкретный пример. Пусть мы имеем число с одинарной точностью, которое в двоичном виде выглядит следующим образом:
1 01111110 11000000000000000000000
Для этого числа знаковый бит равен 1 (отрицательное число), порядок равен 126, мантисса - 11 (в двоичной системе счисления).
Значение этого числа равно:
1.11 * 2(126-127) = -1.75 * 2-1 = -0,875
Рассмотрим теперь особые случаи представления вещественных чисел.
Для большей наглядности сведем все возможные представления вещественных чисел вместе на рис. 10.2.
Рис. 10.2. Возможные предстваления вещественных чисел
Арифметический сопроцессор наряду с вещественными числами способен обрабатывать и целые числа. Он имеет команды, выполняющие преобразования целых чисел в вещественные и обратно.
Возможно четыре формата целых чисел:
Целое число занимает два байта. Его формат полностью соответствует используемому центральным процессором. Для представления отрицательных чисел используется дополнительный код. Короткое целое и длинное целое имеют аналогичные форматы, но занимают, соответственно, 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
На рис. 10.3 мы привели все возможные варианты представления целых чисел.
Рис. 10.3. Возможные представления целых чисел
Формат упакованного десятичного числа показан на рис. 10.4.
Рис. 10.4. Формат упакованного десятичного числа
На этом рисунке n0...n17 означают разряды десятичного числа. Они могут изменяться в пределах от 0000 до 1001, то есть от 0 до 9 в десятичной системе счисления.
Теперь, после того как мы рассмотрели форматы данных, с которыми может работать арифметический сопроцессор, можно перейти к изучению внутренних регистров сопроцессора.
Арифметический сопроцессор содержит восемь численных 80-битовых регистров, предназначенных для хранения промежуточных результатов вычислений, регистра управления, регистра состояния, регистра тегов, регистра указателя команды и регистра указателя операнда.
Мы будем обозначать численные регистры как ST0 - ST7. Они приведены на рисунке 10.5.
Рис. 10.5. Численные регистры арифметического сопроцессора
Численные регистры используются как стек. Регистр состояния в поле ST содержит номер численного регистра, являющего вершиной стека. При выполнении команд в качестве операнда могут выступать численные регистры. В этом случае номер указанного в команде регистра прибавляется к содержимому поля ST регистра состояния и таким образом определяется используемый регистр. Большинство команд после выполнения увеличивают поле ST регистра состояния, как бы записывая результаты своей работы в стек численных регистров.
Вы можете использовать регистры как массив, но в этом случае необходимо заботится о постоянстве поля ST регистра состояния, так как в противном случае номера численных регистров будут изменяться.
Этот регистр разделен на восемь двухбитовых полей, которые мы обозначим как TAG0...TAG7. Каждое поле относится к своему численному регистру (рис. 10.6).
Рис. 10.6. Формат регистра тегов
Поля регистра тегов классифицируют содержимое "своего" численного регистра:
Поле |
Описание |
00 |
Регистр содержит действительное ненулевое число |
01 |
В регистре находится нуль |
10 |
Регистр содержит недействительное число - нечисло, бесконечность, неопределенность |
11 |
Пустой неинициализированный регистр |
Например, если все регистры сопроцессора были пустые, а затем в стек численных регистров было занесено одно действительное ненулевое значение, содержимое регистра тегов будет 3FFFh.
Регистр управления для сопроцессора 8087 показан на рисунке 10.7.
Рис. 10.7. Формат регистра управления для сопроцессора 8087
Регистр управления сопроцессоров 80287/80387 и сопроцессора, входящего в состав более современных процессоров, имеет аналогичный формат, за исключением того, что бит 7 в нем не используется (рис. 10.8).
Рис. 10.8. Формат регистра управления для современных сопроцессоов
Биты 0...5 - маски особых случаев. Особые случаи иногда возникают при выполнении команд сопроцессора, например, при делении на нуль, переполнении и так далее.
Если все биты масок особых случаев равны нулю, особый случай вызывает прерывание центрального процессора INT 10h (обратите внимание, что это прерывание используется BIOS для работы с дисплейным адаптером). Если же особые случаи замаскированы установкой соответствующих бит в единичное состояние, прерывание не вырабатывается, а в качестве результата возвращается особое значение - бесконечность, нечисло и так далее.
Приведем таблицу масок особых случаев:
Маска |
Особый случай |
IM |
Недействительная операция |
DM |
Денормализованный результат |
ZM |
Деление на нуль |
OM |
Переполнение |
UM |
Антипереполнение |
PM |
Особый случай при неточном результате |
IEM |
Маскирование одновременно всех особых случаев вне зависимости от установки бит 0-5 регистра управления. Этот бит действителен только для сопроцессора 8087 |
Подробнее особые случаи и условия их возникновения будут описаны позже, когда мы займемся ошибками при выполнении команд в сопроцессоре.
Поле PC управляет точностью вычислений в сопроцессоре:
Поле |
Описание |
00 |
Использование расширенной точности. Этот режим устанавливается при инициализации сопроцессора |
10 |
Округление результата до двойной точности |
11 |
Округление результата до одинарной точности |
Искусственное ухудшение точности вычислений не приводит к ускорению работы программы. Режимы с пониженной точностью предназначены для эмуляции процессоров, использующих двойную и одинарную точность, соответственно.
Двух битовое поле 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 регистра управления предназначен для управления бесконечностью и может иметь два значения:
В проективном режиме существует только одна бесконечность, она не имеет знака. В афинном режиме имеется две бесконечности - положительная и отрицательная.
Афинный режим допускает выполнение многих операций с бесконечностями - сложение, умножение и так далее.
Поля регистра состояния сопроцессора 8087 показаны на рисунке 10.9.
Рис. 10.9. Формат регистра состояния сопроцессора 8087
Регистр состояния сопроцессоров 80287/80387 и сопроцессора, входящего в состав современных процессоров, имеет немного другой формат. Мы показали его на рис. 10.10.
Рис. 10.10. Формат регистра состояния современных сопроцессоров
В обоих форматах биты 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-разрядный адрес команды, вызвавшей особый случай и код выполняемой в этот момент операции. Адрес команды здесь указывается без учета предшествующих команде префиксов (рис. 10.11).
Рис. 10.11. Указание адреса команды в сопроцессоре 8087
Сопроцессоры 80287/80387 в реальном режиме работы имеют такой же формат регистра указателя команд, однако этот указатель показывает на первый префикс команды, вызвавшей особый случай.
Защищенный режим работы центрального процессора и сопроцессора описан в 6 томе «Библиотеки системного программиста». Для полноты изложения приведем формат указателей и для этого режима.
В защищенном режиме адрес состоит из селектора и смещения. Формат указателя команды для защищенного режима представлен на рисунке 10.12.
Рис. 10.12. Формат указателя команды для защищенного режима
Код операции здесь отсутствует, но его легко получить, пользуясь адресом команды.
Если при возникновении особого случая использовался операнд, находящийся в оперативной памяти, его адрес записывается в регистр указателя операнда. Приведем форматы этого регистра для реального и защищенного режимов работы.
Формат указателя операнда для реального режима представлен на рис. 10.13.
Рис. 10.13. Формат указателя операнда для реального режима
Формат указателя операнда для защищенного режима мы показали на рис. 10.14.
Рис. 10.14. Формат указателя операнда для защищенного режима
Возможны три формата команд сопроцессора, аналогичные форматам команд центральных процессоров фирмы Intel. Это команды с обращением к оперативной памяти, команды с обращением к одному из численных регистров и команды без операндов, заданных явным образом.
Команды с обращением к памяти могут занимать от двух до четырех байт, в зависимости от способа адресации операнда, находящегося в памяти (рис. 10.15).
Рис. 10.15. Формат команд с обращением к памяти
Первые пять бит соответствуют команде центрального процессора ESC. Поля КОП1 и КОП2 определяют выполняемую команду, то есть содержат код операции. Поля MOD и R/M вместе с полями "Смещение1" и "Смещение2" задают адрес операнда в памяти аналогично тому, как это происходит в процессорах. Однако есть и отличия, связанные с возможностью адресации численных регистров сопроцессора.
Ниже мы покажем зависимость способа адресации от содержимого полей MOD и R/M:
Поле | Поле MOD |
| |||
R/M | 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 |
Если в таблице указаны значения смещения disp8 или disp16, это означает, что в команде присуствует один или два байта смещения, соответственно.
Если поле MOD содержит значение 11, возможна адресация численных регистров ST0...ST1. При этом команда не содержит байтов смещения.
Формат команды с обращением к численному регистру приведен на рис. 10.16.
Рис. 10.16. Формат команд с обращением к численному регистру
Видно, что это есть частный случай предыдущей команды, в которой поле MOD содержит значение 11 и отсутствуют байты смещения.
Самый простой формат имеют команды без явного обращения к операндам (рис. 10.17).
Рис. 10.17. Формат команд без явного обращения к операндам
Разумеется, если вы составляете программу для сопроцессора на языке ассемблера, вы можете использовать мнемоническое обозначение команд. Все мнемоники команд сопроцесора начинаются с буквы F, поэтому их легко отличить от команд процессоров.
Все команды сопроцессора можно разделить на несколько групп:
Команды пересылки данных предназначены для загрузки чисел из оперативной памяти в численные регитры, записи данных из численных регистров в оперативную память, копирования данных из одного численного регистра в другой.
Арифметические команды выполняют такие операции, как сложение, вычитание, умножение, деление, извлечение квадратного корня, нахождение частичного остатка, округление и так далее.
Команды сравнения сравнивают вещественные и целые числа, выполняют анализ чисел.
Трансцендентные команды предназначены для вычисления различных тригонометрических, логорифмических, показательных и гиперболических функций - 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, Pentium)
Эти команды пересылают данные из верхушки стека в область памяти, указанную операндом команды. При этом содержимое указателя стека (поля 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 p -> ST(0) - Загрузить число p ("пи") FLDL2T loge10 -> ST(0) - Загрузить loge10 FLDL2E log2e -> ST(0) - Загрузить log2e FLDLG2 log102 -> ST(0) - Загрузить log102 FLDLN2 loge2 -> ST(0) - Загрузить loge2
Загрузка констант выполняется намного быстрее специальными командами, нежели командами загрузки данных из оперативной памяти.
Сопроцессор использует шесть основных типов арифметических команд:
Команда |
Описание |
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) на противоположный.
В центральном процессоре команды условных переходов выполняются в соответствии с установкой отдельных битов регистра флагов процессора. В арифметическом сопроцессоре существуют специальные команды сравнений, по результатам выполнения которых устанавливаются биты кодов условий в регистре состояния:
Команда |
Описание |
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. Биты кодов условий сопроцессора отображаются на регистр флагов центрального процессора таким образом, что для анализа кодов условий можно использовать команды условных переходов.
Например, в следующем фрагменте программы выполняется переход к метке error, если операнды несравнимы:
.286 . . . 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) |
FSIN |
Вычисление sin(x) |
FSINCOS |
Вычисление sin(x) и cos(x) одновременно |
Команда 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). Параметр 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 записывает в память содержимое всех регистров, кроме численных, в формате, показанном на рис. 10.18.
Рис. 10.18. Формат записи в память содержимого всех регистров командой FNSTENV
Команда FLDENV предназначена для загрузки регистров, сохраненных ранее командой FNSTENV. Обе эти команды полезны в программах обработки особых случаев.
Команды FNSAVE и FRSTOR действуют аналогично командам FNSTENV и FLDENV, но они дополнительно сохраняют и восстанавливают содержимое численных регистров. Формат области сохранения регистров, занимающей 94 байта, приведен на рис. 10.19.
Рис. 10.19. Формат записи в память содержимого всех регистров командами FNSAVE и FRSTOR
Команды FINCSTP и FDECSTP увеличивают и уменьшают на 1 указатель стека SP, соответственно.
Команда FFREE ST(i) помечает численный регистр ST(i) как пустой, записывая в соответствующее поле регистра тегов значение 11.
Команда FNOP не производит никаких действий.
Команда FSETPM переводит сопроцессор в защищенный режим работы.
Используя языки высокого уровня, такие как Си или Паскаль, вы можете даже и не знать, что созданная вами программа применяет для вычислений арифметический сопроцессор. В проекте программы вам обычно предоставляется возможность выброа одного из трех вариантов стандартной библиотеки:
Первый вариант (библиотека эмулятора) используется по умолчанию. Программы, которые создаются с использованием эмулятора, будут работать как при наличии в системе сопроцессора, так и при его отсуствии. В последнем случае вычисления с плавающей точкой выполняются специальными подпрограмами, которые присоединяются к вашей программе на этапе редактирования. При запуске ваша программа сама определит факт наличия (или отсуствия) сопроцессора и выберет соответствующий способ выполнения вычислений - либо с использованием сопроцесора, либо с использованием подпрограмм эмуляции сопроцессора.
Все что вам нужно для работы с библиотекой эмуляции - просто выбрать ее при установке системы программирования. Это самый простой способ программирования сопроцессора, когда вам, вообще говоря, совсем не надо его программировать - всю работу по использоанию сопроцессора выполнят модули библиотеки эмуляции.
Второй вариант библиотеки рассчитан на наличие сопроцессора. Если сопроцессора нет, программа работать не будет. Но если известно, что сопроцессор есть (например, процессор Pentium всегда содержит блок арифметики), то имеет смысл использовать именно этот вариант как самый быстродействующий.
Третий вариант не использует сопроцессор совсем. Все вычисления выполняются специальными подпрограммами, входящими в состав библиотеки альтернативной математики и подключающимися к вашей программе автоматически на этапе редактирования.
К сожалению, есть программы, в которых использование библиотеки эмуляции невозможно или крайне затруднительно:
В случае с резидентными программами невозможность использования библиотеки эмулятора вызвана тем, что после оставления программы резидентной в памяти, например, функцией _dos_keep, она теряет доступ к модулям эмуляции.
Механизм вызова программ эмуляции основан на использовании прерываний с номерами 34h - 3Eh. Перед тем как оставить программу резидентной, функция _dos_keep восстанавливает содержимое указанных векторов прерываний, делая невозможным доступ резидентной программе к модулям эмулятора. Да и самих этих модулей уже нет в памяти - на их место может быть загружена новая программа.
Поэтому в руководстве по языку программирования Си рекомендуется для резидентных программ применять библиотеку альтернативной математики. Но эта библиотека, увы, не использует сопроцессор.
Ситуация с драйверами аналогична - драйверы, как правило, составляются на языке ассемблера, поэтому средства эмуляции библиотек Си недоступны.
Выходом может быть непосредственное программирование сопроцессора на языке ассемблера. При этом вы можете полностью использовать все возможности сопроцессора и добиться от программы наибольшей эффективности вычислений.
Какие средства можно использовать для составления программ для сопроцессора?
Обычно это или ассемблер MASM (возможно использование TASM), либо интегрированная среда разработки, содержащая встроенный ассемблер.
Приведем пример самой простой программы NPU1 (листинг 10.1), которая выполняет вычисления по следующей несложной формуле:
z = x + y;
В этой программе значения x и y задаются в виде констант.
Листинг 10.1. Файл npu1\npu1.asm
; ===================================================== ; Простейшая программа для работы с арифметическим ; сопроцессором ; ; (C) A. Frolov, 1997 ; ; E-mail: frolov@glas.apc.org ; WWW: http://www.glasnet.ru/~frolov ; or ; http://www.dials.ccas.ru/frolov ; ===================================================== .model small .STACK 100h .DATA ; Здесь находятся константы с одинарной ; точностью x и y x dd 1.0 y dd 2.0 ; Резервируем четыре байта для результата z dd ? .CODE begin: mov ax, DGROUP mov ds, ax ; Записываем в стек численных регистров ; значение x fld x ; Складываем содержимое верхушки стека ; с константой y fadd y ; Записываем результат в ячейку z fstp z ; Завершаем работу программы и ; возвращаем управление операционной системе mov ax, 4C00h int 21h END begin
Как убедиться в том, что программа работает правильно?
Для этого мы используем отладчик CodeView, содержащий очень удобные средства отладки программ, работающих с арифметическим сопроцессором.
Запустим отладчик CodeView, передав ему в качестве параметра имя приведенной выше программы:
cv npu1.exe
После того, как отладчик запустится, откройте окно регистров сопроцессора. В нижней части экрана появится окно регистров сопроцессора, показанное на рис. 10.20.
Рис.10.20. Окно регистров сопроцессора
Пусть вас не смущает то, что в этом окне пока не показывается состояние регистров сопроцессора. Нажмите клавишу F8, выполнив один шаг программы.
Теперь вы видите содержимое регистров управления и состояния (cControl, cStatus), регистра тегов (cTag), регистров указателей команд и данных (Instr Ptr, Data Ptr), код выполняемой команды (Opcode). Отображается также содержимое стека численных регистров (Stack), но пока это поле пустое, так как все численные регистры отмечены в регистре тегов как пустые.
Нажмите еще раз клавишу F8, выполнив следующую команду программы. Эта команда запишет в стек численных регистров значение переменной x.
Теперь в области регистров стека показано содержимое регистра cST(0), причем как в двоичном виде, так и с использованием экспоненциальной (научной) нотации. Как и следовало ожидать, регистр ST(0) содержит величину 1.0 (рис. 10.21).
Рис. 10.21. В регистре ST(0) находится значение 1.0
Выполним еще одну команду, прибавляющую к содержимому ST(0) значение 2.0 из переменной y. Теперь регистр ST(0) содержит величину 3.0 (рис. 10.22).
Рис. 10.22. Теперь в регистре ST(0) находится значение 3.0
Последняя команда выталкивает из стека хранящееся там значение (3.0) и записывает его в переменную z. Теперь стек численных регистров снова пуст.
В арифметическом сопроцессоре имеются два механизма обработки ошибок, возникающих при выполнении различных команд.
Первый механизм основан на генерации так называемого прерывания особого случая (INT 10h). Это прерывание вырабатывается в том случае, когда происходит какая-нибудь ошибка (например, деление на нуль) при условии, что соответствующие биты масок особых случаев в регистре управления не установлены.
При втором способе обработки ошибок все особые случаи маскируются (соответствующие биты управляющего регистра устанавливаются в единицу) и в случае ошибки сопроцессор в качестве результата возвращает некоторое заранее известное особое значение (нечисло, неопределенность или бесконечность).
Программист может выбирать между этими способами обработки ошибок, маскируя или разрешая прерывание по особому случаю. Если прерывание особого случая замаскировано, можно предложить следующий способ обнаружения ошибки:
Кроме того, после выполнения команды полезно проверить получившийся результат на принадлежность к множеству особых значений.
Рассмотрим возможные особые случаи сопроцессора в реальном режиме.
В результате выполнения некоторых операций может возникнуть такая ситуация, когда невозможно точно представить результат. Например, при результатом деления числа 1.0 на 3.0 является бесконечная периодическая двоичная дробь 0.010101... Такое число не может быть представлено точно ни в одном формате вещественных чисел.
Обычно неточный результат является результатом округления и может не рассматриваться как ошибка.
Если результат выполнения операции слишком велик и не может быть представлен в формате приемника результата, фиксируется особый случай переполнения.
Этот особый случай обязательно произойдет, например, при сложении максимального числа расширенной точности самим с собой или при преобразовании этого числа в формат с двойной или одинарной точностью.
Так как для хранения промежуточных результатов используется 80-битовое представление, при выполнении операций над числами с одинарной или двойной точностью переполнения, как правило, не происходит. Огромный диапазон чисел с расширенной точностью гарантирует правильность представления больших по абсолютной величине результатов операций с числами одинарной и двойной точности.
Антипереполнение возникает тогда, когда результат слишком мал для его представления в формате приемника результата операции, но все же отличен от нуля. Например, если делается попытка преобразовать наименьшее положительное число с расширенной точностью в формат числа с двойной или одинарной точностью.
Если вы используете числа только с двойной или одинарной точностью, а для хранения промежуточных результатов используете формат с расширенной точностью, особый случай антипереполнения, как правило, не возникает.
Этот особый случай возникает при попытке выполнить деление конечного ненулевого числа на нуль.
В афинном режиме при делении конечных (положительных или отрицательных) чисел на нуль (положительный или отрицательный) в качестве результата возвращается бесконечность. Знак этой бесконечности зависит от знака делимого и от знака нуля. Например, при делении положительного ненулевого числа на положительный нуль получается положительная бесконечность, при делении положительного ненулевого числа на отрицательный нуль - отрицательная бесконечность.
В проективном режиме, а также при попытке деления нуля на нуль возникает особый случай недействительной операции, который будет рассмотрен ниже.
Этот особый случай возникает при попытке выполнения таких запрещенных команд, как деление нуля на нуль, извлечения корня из отрицательного числа, обращение к несуществующему регистру сопроцессора или при попытке использования в качестве операндов команд нечисел, неопределенностей или бесконечности (для трансцендентных функций).
Мы уже говорили о том, что сопроцессор использует операнды в нормализованной форме. Однако при выполнении операции может оказаться, что результат слишком мал по абсолютной величине для представления его в нормализованной форме. Можно было бы считать такой результат нулевым, однако это привело бы к снижению точности вычислений или даже к грубым ошибкам. Например, вычисляется следующее выражение:
(y-x)+x;
Если разность (y-x) вызывает антипереполнение и в качестве результата берется нулевое значение, то после вычисления всего выражения получится x. Если же пойти на расширение диапазона представления чисел за счет снижения точности и сформировать результат вычисления разности (y-x) как денормализованное число, выражение будет вычислено правильно и в результате получится y.
Таким образом, иногда целесообразно замаскировать особый случай денормализованного операнда и использовать денормализованные числа. Однако при попытке деления на ненормализованное число или извлечения из него квадратного корня фиксируется особый случай недействительной операции.
В листинге 10.2 мы привели исходный текст программы PENTERR, с помощью которой можно обнаружить известную ошибку в арифметическом сопроцессоре, встроенном в процессор Pentium. Эта ошибка приводит к снижению точности результата при делении некоторых комбинаций чисел.
В нашем случае мы проверяем результат вычислений по следующей формуле:
(4195835 / 3145727) * 3145727
Если ошибки нет, в результате вычислений должно получиться значение 4195835. При наличии ошибки значение результата будет меньше этого числа на 256.
Листинг 10.2. Файл penterr\penterr.asm
; ===================================================== ; Проверка наличия ошибки в сопроцессоре, ; встроенном в процессор Pentium ; ; (C) A. Frolov, 1997 ; ; E-mail: frolov@glas.apc.org ; WWW: http://www.glasnet.ru/~frolov ; or ; http://www.dials.ccas.ru/frolov ; ===================================================== .model small .STACK 100h .DATA .286 x dd 4195835.0 y dd 3145727.0 z dd ? BugMsg db 13,10,"Pentium NPU bug detected!", "$" OkMsg db 13,10,"Your Pentium NPU is OK", "$" .CODE begin: mov ax, DGROUP mov ds, ax ; Записываем в стек численных регистров ; значение x fld x ; Делим содержимое верхушки стека ; на константу y fdiv y ; Умножаем содержимое верхушки стека ; на эту же константу fmul y ; В результате при отсутствии ошибки мы должны ; получить результат, равный x fcom x ; Сохраняем регистр состояния сопроцессора в AX fstsw ax ; Переписываем AH в регистр флагов sahf ; Проверяем равенство нулю jnz bug ; Ошибки нет mov ah, 9 mov dx, offset OkMsg int 21h jmp next ; Обнаружена ошибка bug: mov ah, 9 mov dx, offset BugMsg int 21h ; Завершаем работу программы и ; возвращаем управление операционной системе next: mov ax, 4C00h int 21h END begin