Intel 8087浮点芯片的指令解码

Ken Shirriff 新闻

摘要

对Intel 8087浮点协处理器指令解码的详细逆向工程分析,解释主CPU与协处理器之间的交互、微码ROM的使用以及总线接口单元。

<style> pre {border:none;} </style> 在20世纪80年代,如果你想让IBM PC运行得更快,可以购买Intel 8087浮点协处理器芯片。有了这款芯片,CAD软件、电子表格、飞行模拟器和其他程序的速度都大幅提升。8087芯片当然可以执行加、减、乘、除运算,还能计算正切和对数等超越函数,并提供π等常数。总体而言,8087为计算机增加了62条新指令。</p> <p>但是,PC如何判断一条指令是面向8087的浮点指令,还是面向8086或8088 CPU的普通指令呢?8087芯片又是如何解释指令以确定其含义的?实际上,8087内部的指令解码比预期的要复杂得多。8087采用了多种技术,解码电路遍布整个芯片。在这篇博文中,我将解释这些解码电路的工作原理。</p> <p>为了对8087进行逆向工程,我撬开了8087芯片的陶瓷封装,并用显微镜拍摄了大量硅片的照片。芯片上复杂的图案由金属布线以及下方的多晶硅和硅形成。芯片的下半部分是“数据路径”,即执行80位浮点值计算的电路。数据路径左侧是一个<a href="https://www.righto.com/2020/05/extracting-rom-constants-from-8087-math.html">常量ROM</a>,存储着π等重要常量。右侧是程序员用于保存浮点值的八个寄存器;一个不寻常的设计决策是,这些寄存器以<a href="https://www.righto.com/2025/12/8087-stack-circuitry.html">栈</a>的形式排列。浮点数通过小数部分和指数来表示大范围数值;8087具有单独的处理小数部分和指数的电路。</p> <p><a href="https://static.righto.com/images/8087-decode/8087-die-labeled.jpg"><img alt="Intel 8087浮点单元芯片的裸片,主要功能模块已标注。裸片尺寸为5 mm×6 mm。点击此图(或任何其他图片)可查看大图。" class="hilite" height="587" src="https://static.righto.com/images/8087-decode/8087-die-labeled-w450.jpg" title="Intel 8087浮点单元芯片的裸片,主要功能模块已标注。裸片尺寸为5 mm×6 mm。点击此图(或任何其他图片)可查看大图。" width="450" /></a><div class="cite">Intel 8087浮点单元芯片的裸片,主要功能模块已标注。裸片尺寸为5 mm×6 mm。点击此图(或任何其他图片)可查看大图。</div></p> <p>芯片的指令由中间的大型<a href="https://www.righto.com/2018/09/two-bits-per-transistor-high-density.html">微码ROM</a>定义。<span id="fnref:microcode"><a class="ref" href="#fn:microcode">1</a></span> 要执行一条指令,8087会对其解码,然后微码引擎开始从微码ROM执行相应的微指令。在芯片的右上方,总线接口单元(BIU)通过计算机总线与主处理器和内存通信。大多数情况下,BIU和芯片的其他部分独立运行,但我们将看到,BIU在指令解码和执行中扮演重要角色。</p> <h2>与主8086/8088处理器的协作</h2> <p>8087芯片作为协处理器与主8086(或8088)处理器协同工作。当遇到浮点指令时,8086会让8087浮点芯片执行该指令。但是,8086和8087如何确定哪个芯片执行特定指令?你可能期望8086告知8087何时执行指令,但这种协作实际上更为复杂。</p> <p>8086有八个分配给协处理器的操作码,称为<code>ESCAPE</code>操作码。8087通过监听总线来确定8086正在执行的指令,这一任务由BIU(总线接口单元)执行。<span id="fnref:queue"><a class="ref" href="#fn:queue">2</a></span> 如果指令是<code>ESCAPE</code>,则该指令是为8087准备的。然而,存在一个问题。8087无法访问8086的寄存器(反之亦然),因此它们交换数据的唯一方式是通过内存。但8086通过一种涉及偏移寄存器和段寄存器的复杂方案来寻址内存。8087无法访问这些寄存器,如何确定要使用的内存地址呢?</p> <p>关键在于,当遇到<code>ESCAPE</code>指令时,即使该指令是为8087准备的,8086处理器仍会开始执行它。8086计算指令引用的内存地址并读取该地址,但忽略结果。同时,8087监听内存总线以查看访问的地址,并在BIU寄存器中内部存储该地址。当8087开始执行指令时,它会使用来自8086的地址来读写内存。实际上,8087将地址计算任务卸载给了8086处理器。</p> <h2>8087指令的结构</h2> <p>要理解8087的指令,我们需要更仔细地查看8086指令的结构。特别是,ModR/M字节非常重要,因为所有8087指令都使用它。</p> <p>8086使用复杂的操作码系统,混合了单字节操作码、前缀字节和更长指令。约四分之一的操作码使用第二个字节(称为ModR/M),通过复杂编码指定要使用的寄存器和/或内存地址。例如,内存地址可以通过BX和SI寄存器相加计算,或由BP寄存器加上两字节偏移量得到。ModR/M字节的前两位是“MOD”位。对于内存访问,MOD位指示在ModR/M字节之后有多少地址位移字节(0、1或2),而“R/M”位指定地址的计算方式。然而,MOD值为3表示指令操作寄存器,不访问内存。</p> <p><a href="https://static.righto.com/images/8087-decode/modrm.jpg"><img alt="8087指令的结构" class="hilite" height="122" src="https://static.righto.com/images/8087-decode/modrm-w600.jpg" title="8087指令的结构" width="600" /></a><div class="cite">8087指令的结构</div></p> <p>上图显示一条8087指令由一个<code>ESCAPE</code>操作码后跟一个ModR/M字节组成。<code>ESCAPE</code>操作码由特殊位模式<code>11011</code>指示,在第一个字节中留出三个位(绿色)来指定8087指令的类型。如上所述,ModR/M字节有两种形式。第一种形式执行内存访问;其MOD位为<code>00</code>、<code>01</code>或<code>10</code>,R/M位指定内存地址的计算方式。这留出三个位(绿色)来指定地址。第二种形式在内部运行,无需内存访问;其MOD位为<code>11</code>。由于第二种形式不使用R/M位,R/M字节中可用六个位(绿色)来指定指令。</p> <p>8087设计者面临的挑战是,将所有指令适配到可用位中,同时使解码过程简单直接。下图展示了几条8087指令,说明了它们如何实现这一点。前三条指令在内部运行,因此MOD位为11;绿色位指定具体指令。加法更为复杂,因为它可以根据<code>MOD</code>位对内存(第一种格式)或寄存器(第二种格式)进行操作。高亮的四位
查看原文
查看缓存全文

缓存时间: 2026/05/16 03:32

# Intel 8087 浮点芯片中的指令解码 **来源:** <http://www.righto.com/2026/02/8087-instruction-decoding.html> 在 20 世纪 80 年代,如果你想让 IBM PC 运行得更快,可以购买 Intel 8087 浮点协处理器芯片。有了这颗芯片,CAD 软件、电子表格、飞行模拟器和其他程序的速度都会大幅提升。8087 芯片不仅能执行加、减、乘、除运算,还能计算正切、对数等超越函数,并提供 π 等常数。总体而言,8087 为计算机增加了 62 条新指令。 但是,PC 如何判断一条指令是 8087 的浮点指令,还是 8086 或 8088 CPU 的常规指令呢?8087 芯片又是如何解释指令以确定其含义的呢?事实证明,在 8087 内部解码一条指令比你想象的要复杂。8087 使用了多种技术,解码电路遍布整个芯片。在这篇博文中,我将解释这些解码电路的工作原理。 为了对 8087 进行逆向工程,我凿开了 8087 芯片的陶瓷封装,并用显微镜拍摄了大量硅裸片的照片。裸片上的复杂图案由金属布线以及其下方的多晶硅和硅构成。芯片的下半部分是“数据通路”,即执行 80 位浮点值计算的电路。在数据通路的左侧,一个**常量 ROM** 存储了 π 等重要常量。右侧是八个寄存器,供程序员保存浮点值;一个不寻常的设计是,这些寄存器被组织成一个**栈**。浮点数通过小数部分和指数来表示极大的数值范围;8087 有独立的电路来处理小数部分和指数。 Intel 8087 浮点单元芯片的裸片,标有主要功能块。裸片尺寸为 5 mm×6 mm。点击此图像(或任何其他图像)可查看大图。 芯片的指令由中央的大型**微码 ROM** 定义。¹ 为了执行一条指令,8087 会解码该指令,然后微码引擎开始从微码 ROM 中执行适当的微指令。在芯片的右上部分,总线接口单元 (BIU) 通过计算机总线与主处理器和内存通信。大多数情况下,BIU 和芯片的其他部分独立运行,但正如我们将看到的,BIU 在指令解码和执行中扮演着重要角色。 ## 与主 8086/8088 处理器的协作 8087 芯片作为主 8086(或 8088)处理器的协处理器工作。当遇到浮点指令时,8086 会让 8087 浮点芯片执行该浮点指令。但是,8086 和 8087 如何确定哪个芯片执行特定指令?你可能会认为 8086 会告诉 8087 何时执行指令,但这种协作实际上更为复杂。 8086 有八个操作码分配给协处理器,称为 `ESC`(Escape)操作码。8087 通过监视总线来确定 8086 正在执行什么指令,这项任务由 BIU(总线接口单元)执行。² 如果指令是 `ESC`,则该指令是为 8087 准备的。然而,这里有一个问题。8087 无法访问 8086 的寄存器(反之亦然),因此它们交换数据的唯一方式是通过内存。但 8086 通过涉及偏移寄存器和段寄存器的复杂方案来寻址内存。当 8087 无法访问这些寄存器时,它如何确定要使用的内存地址? 诀窍在于,当遇到 `ESC` 指令时,8086 处理器会开始执行该指令,即使它本来是为 8087 准备的。8086 计算该指令引用的内存地址,并读取该内存地址,但忽略结果。与此同时,8087 监视内存总线,查看访问了哪个地址,并将此地址内部存储在 BIU 寄存器中。当 8087 开始执行指令时,它使用从 8086 获得的地址来读写内存。实际上,8087 将地址计算的工作卸载给了 8086 处理器。 ## 8087 指令的结构 要理解 8087 的指令,我们需要更仔细地研究 8086 指令的结构。特别地,一个称为 ModR/M 的字节非常重要,因为所有 8087 指令都使用它。 8086 使用复杂的操作码系统,混合了单字节操作码、前缀字节和更长指令。大约四分之一的操作码使用第二个字节,称为 ModR/M,通过复杂的编码来指定要使用的寄存器和/或内存地址。例如,内存地址可以通过将 BX 和 SI 寄存器相加来计算,或者通过 BP 寄存器加上一个两字节偏移量来计算。ModR/M 字节的前两位是“MOD”位。对于内存访问,MOD 位指示在 ModR/M 字节之后有多少个地址偏移字节(0、1 或 2),而“R/M”位则指定如何计算地址。然而,MOD 值为 3 表示该指令操作寄存器,不访问内存。 **一张 8087 指令的结构图** 上图展示了一条 8087 指令如何由一个 `ESC` 操作码后跟一个 ModR/M 字节组成。`ESC` 操作码由特殊的位模式 `11011` 指示,在第一个字节中剩下三个位(绿色)用于指定 8087 指令的类型。如上所述,ModR/M 字节有两种形式。第一种形式执行内存访问;其 MOD 位为 `00`、`01` 或 `10`,R/M 位指定如何计算内存地址。这剩下三个位(绿色)用于指定地址。第二种形式在内部运行,不访问内存;其 MOD 位为 `11`。由于第二种形式不使用 R/M 位,因此在 R/M 字节中有六个位(绿色)可用于指定指令。 8087 设计者面临的挑战是如何将所有指令适配到可用的位中,同时使解码变得直接。下图展示了一些 8087 指令,说明了它们是如何实现这一点的。前三条指令在内部运行,因此它们的 MOD 位为 11;绿色位指定具体的指令。加法更为复杂,因为它可以对内存操作(第一种格式)或寄存器操作(第二种格式),具体取决于 `MOD` 位。以亮绿色突出显示的四个位 (`0000`) 对于所有 `ADD` 指令都是相同的;减法、乘法、除法指令使用相同的结构,但深绿色位具有不同的值。例如,`0001` 表示乘法,`0100` 表示减法。其他绿色位(`MF`、`d` 和 `P`)选择加法指令的变体,改变数据格式、方向以及在结束时弹出栈。最后三位选择内存操作的 R/M 寻址模式,或寄存器操作的栈寄存器 `ST(i)`。 **一些 8087 指令的位模式。基于数据手册。** (来源: datasheet) ## 选择微码例程 大多数 8087 指令都用微码实现,将指令的每一步拆分为底层的“微指令”。8087 芯片包含一个微码引擎;你可以将其视为控制 8087 的迷你 CPU,它一条接一条地执行微码例程中的微指令。微码引擎向 ROM 提供一个 11 位的微地址,指定要执行的微指令。通常,微码引擎按顺序执行微码,但它也支持条件跳转和子程序调用。 但是,微码引擎如何知道从何处开始执行特定机器指令的微码呢?从概念上讲,你可以将指令操作码输入一个 ROM,该 ROM 会提供起始微地址。然而,这并不实用,因为你需要一个 2048 字的 ROM 来解码 11 位操作码。³(虽然如今 2K ROM 很小,但在当时却很大;8087 的微码 ROM 容量为 1648 个字,已是紧巴巴的。)相反,8087 使用了一个更高效(但也更复杂)的指令解码系统,该系统由逻辑门和 PLA(可编程逻辑阵列)组合而成。这个系统包含 22 个微码入口点,比 2048 个实用得多。 处理器通常使用一种称为 PLA(可编程逻辑阵列)的电路作为指令解码的一部分。PLA 的思想是提供一种密集而灵活的方式来实现任意的逻辑功能。任何布尔逻辑函数都可以表示为“乘积之和”,即一组 AND 项(乘积)通过 OR(求和)组合在一起。PLA 有一块称为 AND 平面的电路,用于生成所需的乘积项。AND 平面的输出被馈送到第二个块,即 OR 平面,该平面将各项 OR 在一起。物理上,PLA 实现为一个网格,网格中的每个点可以放置或不放置晶体管。通过改变晶体管模式,PLA 实现所需的功能。 **一张简化后的 PLA 结构图** PLA 可以实现任意逻辑,但在 8087 中,PLA 通常作为优化的 ROM 使用。⁴ AND 平面匹配位模式,⁵ 从 OR 平面中选择一个条目,该条目包含输出值,即每个例程的微地址。PLA 相对于标准 ROM 的优势在于,一个输出列可以用于许多不同的输入,从而减小了尺寸。 下图显示了指令解码 PLA 的一部分。⁶ 水平输入线是位于硅之上的多晶硅导线。粉红色区域是掺杂硅。当多晶硅跨越掺杂硅时,会产生一个晶体管(绿色)。当掺杂硅中存在间隙时,则没有晶体管(红色)。(输出线垂直运行,但在此处不可见;我溶解了金属层以显示下面的硅。)如果一条多晶硅线被激活,它会打开该行中的所有晶体管,将相关的输出列拉低到地。(如果没有晶体管被打开,上拉晶体管会将输出拉高。)因此,掺杂硅区域的模式在 PLA 中创建了一个晶体管网格,实现了所需的逻辑功能。⁷ **指令解码 PLA 的一部分。** 使用 PLA 解码指令的标准方法是将指令位(及其补码)作为输入。然后,PLA 可以针对指令中的位模式进行模式匹配。然而,8087 还使用了一些预处理来减小 PLA 的尺寸。例如,`MOD` 位被处理,如果位为 0、1 或 2(即内存操作),则生成一个信号;如果位为 3(即寄存器操作),则生成第二个信号。这使得 0、1 和 2 的情况可以由单个 PLA 模式处理。另一个信号指示高位为 `001 111xxxxx`;这表示 R/M 字段参与了指令选择。⁸ 有时,PLA 输出会作为输入反馈回去,这样解码出的指令组可以从另一组中排除。这些技术都以增加一些额外逻辑门为代价,减小了 PLA 的尺寸。 指令解码 PLA 的 AND 平面的结果是 22 个信号,每个信号对应一条指令或一组具有共享微码入口点的指令。指令解码 PLA 的下半部分充当 ROM,保存着 22 个微码入口点,并提供选中的那个。⁹ ## 微码内部的指令解码 许多 8087 指令共享相同的微码例程。例如,加法、减法、乘法、除法、反向减法和反向除法指令都指向同一个微码例程。这减小了微码的尺寸,因为这些指令共享设置指令和处理结果的微码。然而,在某个点上,微码显然需要分叉以执行特定的操作。此外,一些算术操作码访问栈顶,一些访问栈中的任意位置,一些访问内存,还有一些反转操作数,这需要不同的微码操作。微码如何在共享代码的同时对不同操作码执行不同操作? 诀窍在于,8087 的微码引擎支持基于 49 种不同条件的条件子程序调用、返回和跳转(详情)。特别是,有十五种条件会检查指令。一些条件测试特定的位模式,例如如果最低位被设置则分支,或者更复杂的模式如操作码匹配 `0xx 11xxxxxx`。其他条件检测特定指令,如 `FMUL`。结果是,微码可以为不同指令采用不同路径。例如,反向减法或反向除法在微码中通过测试指令并在必要时反转参数来实现,同时共享其余代码。 微码还有一个特殊的跳转目标,该目标根据当前正在执行的机器指令执行三路跳转。微码引擎有一个跳转 ROM,保存着 22 个跳转或子程序调用的入口点。¹⁰ 然而,对目标 0 的跳转会使用特殊电路,因此它会根据指令跳转到目标 1(乘法指令)、目标 2(加/减法)或目标 3(除法)。这个特殊跳转由跳转解码器右上角区域的门电路实现。 **跳转解码器和 ROM。注意,各行的顺序并非按数字排列;这可能使得布局稍微紧凑一些。点击此图像(或任何其他图像)可查看大图。**

相似文章