80386虽然是一个通用微处理器,但其指令并不是非常适用于数学计算。因此若使用80386来执行数学计算,那么就需要编制非常复杂的程序,而且执行效率也相对较低。80387作为80386的辅助处理芯片,极大地扩展了程序员的编程范围。以前程序员不太可能做到的事,使用协处理器后就可以很容易地,并且快速而精确地完成。
80387具有一组特别的寄存器。这组寄存器可以让80387直接操作比80386所能处理的大或小几个数量级的数值。80386使用2进制补数方式表示一个数。这种方法不适合用来表示小数。而80387并不使用2的补数方法来表示数值,它使用了IEEE标准754规定的80位(10个字节)格式。这种格式不仅具有广泛的兼容性,而且能够使用二进制表示极大(或极小)的数值。例如,它能表示大到1.21×[I]498)this.width=498;' onmousewheel = 'javascript:return big(this)' height=28 alt="" src="http://pic.aIhUaU.com/201602/15/211815954.jpg" width=58 border=0> 数值,也能处理小到3.3×[I]498)this.width=498;' onmousewheel = 'javascript:return big(this)' height=25 alt="" src="http://pic.aIhUaU.com/201602/15/211826522.jpg" width=66 border=0> 的数。80387并不保持固定小数点的位置,如果数值小的话就多使用一些小数位,如果数值大的话就少用几位小数位。因此小数点的位置是可以"浮动"的。这也是术语"浮点"数的由来。
为支持浮点运算,80387中包含三组寄存器,如图11-5所示。① 8个80位长的数据寄存器(累加器),可用于临时存放8个浮点操作数,并且这些累加器可以执行栈式操作;② 3个16位状态和控制寄存器:一个状态字寄存器SWD、一个控制字寄存器CWD和一个特征(TAG)寄存器;③ 4个32位出错指针寄存器(FIP、FCS、FOO和FOS)用于确定导致80387内部异常的指令和内存操作数。
[TR]
[TD][I]498)this.width=498;' onmousewheel = 'javascript:return big(this)' height=467 alt="" src="http://pic.aIhUaU.com/201602/15/211940857.jpg" width=823 border=0>[/TD][/TR]
[TR]
[TD]图11-5 80387的寄存器[/TD][/TR]
1.栈式浮点累加器
在浮点指令执行过程中,8个80位长度的物理寄存器组被作为栈式累加器使用。虽然每个80位寄存器有固定的物理顺序位置(即左边的0~7),但当前栈顶则由ST(即ST(0))来指明。ST之下的其余累加器使用名称ST(i)来指明(i = 1~7)。至于哪个80位物理寄存器是当前栈顶ST,则由具体操作过程指定。在状态字寄存器中名称为TOP的3位字段含有当前栈顶ST对应的80位物理寄存器的绝对位置。一个入栈(Push)操作将会把TOP字段值递减1,并把新值存储于新的ST中。在入栈操作之后,原来的ST变成了ST(1),而原来的ST(7)变成了现在的ST。即所有累加器的名称都从原来的ST(i)变成了ST((i+1)&0x7)。一个出栈(Pop)操作将会读出当前ST对应的80位寄存器的值,并且把TOP字段值递增1。因此在出栈操作之后,原来的ST(即ST(0))变成了ST(7),原来的ST(1)成为新的ST。即所有累加器的名称都从原来的ST(i)变成ST((i 1)& 0x7)。
ST的作用如同一个累加器是因为它被作为所有浮点指令的一个隐含操作数。若有另一个操作数,那么该第2个操作数可以是任何其余累加器之一ST(i),或者是一个内存操作数。栈中的每个累加器为一个实数提供了使用临时实数格式存储的80位空间,其最高位(s)是符号位,位78~64是15位的指数字段,位63~0是64位的有效数字段。
浮点指令被设计成能充分利用这个累加器栈模式。浮点加载指令(FLD等)会从内存中读取一个操作数并压入栈中,而浮点存储指令则会从当前栈顶取得一个值并写到内存中。若栈中该值不再需要时还可以同时执行出栈操作。加和乘之类的操作会把当前ST寄存器内容作为一个操作数,而另一个取自其他寄存器或内存中,并且在计算完后即把结果保存在ST中。还有一类"操作并弹出"操作形式用于在ST和ST(1)两者之间进行运算。这种操作形式会执行一次弹出操作,然后把结果放入新的ST中。
2.状态与控制寄存器
三个16位的寄存器(TAG字、控制字和状态字)控制着浮点指令的操作并且为其提供状态信息。它们的具体格式如图11-6所示。下面逐一对它们进行说明。
(1)控制字
控制字(Control Word)可用于程序设置各种处理选项来控制80387的操作。其中可分为三个部分。位11~10的RC(Rounding Control)是舍入控制字段,用于对计算结果进行舍入操作。位9~8的PC(Precision Control)是精度控制字段,用于在保存到指定存储单元之前对计算结果进行精度调整。所有其他操作使用临时实数格式精度,或者使用指令指定的精度。位5~0是异常屏蔽位,用于控制协处理器异常处理。这6位对应80387可能发生的6种异常情况。其中每一种异常都可以单独屏蔽掉。如果发生某个特定异常并且其对应屏蔽位没有置位,那么80387就会向CPU通报这个异常,并且会让CPU产生异常中断int 16。然而如果设置了对应屏蔽位,那么80387就会自己处理并纠正发生的异常问题而不会通知CPU。这个寄存器随时可以读写,其中各位的具体含义参见图11-6。
(2)状态字
在运行期间,80387会设置状态字(Status Word)中的位,用于程序检测特定的条件。当发生异常时,它可让CPU确定发生异常的原因。因为所有6个协处理器异常都会让CPU产生异常中断int16。
(3)特征字
特征字(Tag Word)寄存器含有8个2位的Tag字段,分别对应8个物理浮点数据寄存器。这些特征字段分别指明相应的物理寄存器含有有效、零、特殊浮点数值,或者是空的。特殊数值是指那些无限值、非数值、非规格化或不支持格式的数值。特征字段Tag可用于检测累加器堆栈上下溢出情况。如果入栈(Push)操作递减TOP指向了一个非空寄存器,就会发生栈上溢出。如果出栈(Pop)操作企图去读取或弹出空寄存器,就会造成栈下溢出(Underflow)。栈的上下溢出都将引发无效操作异常。
[TR]
[TD][I]498)this.width=498;' onmousewheel = 'javascript:return big(this)' height=665 alt="" src="http://pic.aIhUaU.com/201602/15/212115488.jpg" width=821 border=0>[/TD][/TR]
[TR]
[TD]图11-6 控制和状态寄存器格式[/TD][/TR]
3.出错指针寄存器
出错指针寄存器(Error-Pointer Register)是4个32位的80387寄存器,其中含有80387最后执行指令和所用数据的指针,参见图11-6。前两个寄存器FIP和FCS中是最后执行指令中2个操作码的指针(忽略前缀码)。FCS是段选择符和操作码,FIP是段内偏移值。后两个寄存器FOO和FOS是最后执行指令内存操作数的指针。FOS中是段选择符,FOO中是段内偏移值。如果最后执行的协处理器指令不含内存操作数,则后两个寄存器值无用。指令FLDENV、FSTENV、FNSTENV、FRSTOR、FSAVE和FNSAVE用于加载和保存这4个寄存器的内容。前3条指令共加载或保存28字节内容:控制字、状态字和特征字以及4个出错指针寄存器。控制字、状态字和特征字都以32位操作,高16位为0。后3条指令用于加载或保存协处理器所有108字节的寄存器内容。
4.浮点指令格式
对协处理器进行仿真就是解析具体的浮点指令操作码和操作数,根据每一条指令的结构使用80386的普通指令来执行相应的仿真操作。数学协处理器80387共有七十多条指令,共分5类,见表11-4。每条指令的操作码都有2个字节,其中第一个字节高5位都是二进制11011。这5位的数值(0x1b或十进制27)正好是字符ESC(转义)的ASCII代码值,因此所有数学协处理器指令都被形象地称为ESC转义指令。在仿真浮点指令时可忽略相同的ESC位,只要判断低11位的值即可。
表11-4 浮点指令类型
[TR]
[TD]
[/TD]
[TD]
第1字节
[/TD]
[TD]
第2字节
[/TD]
[TD]
可选字段
[/TD][/TR]
[TR]
[TD]
1
[/TD]
[TD]
1 1 0 1 1
[/TD]
[TD]
OPA
[/TD]
[TD]
1
[/TD]
[TD]
MOD
[/TD]
[TD]
1
[/TD]
[TD]
OPB
[/TD]
[TD]
R/M
[/TD]
[TD]
SIB
[/TD]
[TD]
DISP
[/TD][/TR]
[TR]
[TD]
2
[/TD]
[TD]
1 1 0 1 1
[/TD]
[TD]
MF
[/TD]
[TD]
OPA
[/TD]
[TD]
MOD
[/TD]
[TD]
OPB
[/TD]
[TD]
R/M
[/TD]
[TD]
SIB
[/TD]
[TD]
DISP
[/TD][/TR]
[TR]
[TD]
3
[/TD]
[TD]
1 1 0 1 1
[/TD]
[TD]
d
[/TD]
[TD]
P
[/TD]
[TD]
OPA
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
OPB
[/TD]
[TD]
ST(i)
[/TD]
[TD]
[/TD]
[TD]
[/TD][/TR]
[TR]
[TD]
4
[/TD]
[TD]
1 1 0 1 1
[/TD]
[TD]
0
[/TD]
[TD]
0
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
OP
[/TD]
[TD]
[/TD]
[TD]
[/TD][/TR]
[TR]
[TD]
5
[/TD]
[TD]
1 1 0 1 1
[/TD]
[TD]
0
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
1
[/TD]
[TD]
OP
[/TD]
[TD]
[/TD]
[TD]
[/TD][/TR]
[TR]
[TD]
[/TD]
[TD]
15~11
[/TD]
[TD]
10
[/TD]
[TD]
9
[/TD]
[TD]
8
[/TD]
[TD]
7
[/TD]
[TD]
6
[/TD]
[TD]
5
[/TD]
[TD]
4 3 2 1 0
[/TD]
[TD]
[/TD]
[TD]
[/TD][/TR]
表中各个字段的含义如下(有关这些字段的具体含义和详细说明请参考80x86处理器手册):
1)OP(Operation opcode)是指令操作码,在有些指令中它被分成了OPA和OPB两部分。
2)MF(Memory Format)是内存格式。00:32位实数;01:32位整数;10:64位实数;11:64位整数。
3)P(Pop)指明在操作后是否要执行一次出栈处理。0:不需要;1:操作后弹出栈。
4)d(destination)指明保存操作结果的累加器。0:ST(0);1:ST(i)。
5)MOD(Mode)和R/M(Register/Memory)是操作方式字段和操作数位置字段。
6)SIB(Scale Index Base)和DISP(Displacement)是具有MOD和R/M字段指令的可选后续字段。
另外,所有浮点指令的汇编语言助记符都以字母F开头,例如:FADD、FLD等。还有如下一些标准表示方法:
1)FI所有操作整型数据的指令都以FI开头,例如FIADD、FILD等。
2)FB所有操作BCD类型数据的指令都以FB开头,例如FBLD、FBST等。
3)FxxP所有会执行一次出栈操作的指令均以字母P结尾,例如FSTP、FADDP等。
4)FxxPP所有会执行二次出栈操作的指令均以字母PP结尾,例如FCOMPP、FUCOMPP等。
5)FNxx除了以FN开头的指令,所有指令在执行前都会先检测未屏蔽的运算异常。而以FN开头的指令不检测运算异常情况,例如FNINIT、FNSAVE等。