Intel 8087 浮点芯片微码中的条件

Ken Shirriff 新闻

摘要

对 Intel 8087 浮点协处理器微码中使用的条件测试的详细研究,是逆向工程工作的一部分,旨在理解其算法。

<p>在 20 世纪 80 年代,如果你想让计算机更快地执行浮点运算,你可以购买 Intel 8087 浮点协处理器芯片。将它插入你的 IBM PC 后,运算速度可提升多达 100 倍,这对电子表格和其他数值密集型应用来说是巨大的提升。8087 使用复杂的算法来计算三角函数、对数和指数函数。这些算法在芯片内部通过微码实现。我是一个逆向工程该微码的团队的一员。在本文中,我研究了 8087 微码在其算法中使用的 49 种条件测试。有些条件很简单,比如检查一个数是否为零或负数,而另一些则很特殊,比如确定数字的舍入方向。</p> <p>为了探索 8087 的电路,我打开了一个 8087 芯片,并用显微镜拍摄了大量硅裸片的照片。在裸片的边缘,你可以看到连接芯片到其 40 个外部引脚的细如发丝的键合线。裸片上复杂的图案由金属布线以及下面的多晶硅和硅形成。芯片的下半部分是“数据通路”,即执行 80 位浮点值计算的电路。在数据通路的左侧,一个常量 ROM 保存着重要常量,如 π。右侧是八个寄存器,程序员用它们来保存浮点值;在一个不寻常的设计决策中,这些寄存器被安排为栈。</p> <p><a href="https://static.righto.com/images/8087-conditions/8087-die-labeled.jpg"><img alt="Intel 8087 浮点单元芯片的裸片,标注了主要功能块。裸片尺寸为 5mm×6mm。点击查看大图。" class="hilite" height="587" src="https://static.righto.com/images/8087-conditions/8087-die-labeled-w450.jpg" title="Intel 8087 浮点单元芯片的裸片,标注了主要功能块。裸片尺寸为 5mm×6mm。点击查看大图。" width="450" /></a><div class="cite">Intel 8087 浮点单元芯片的裸片,标注了主要功能块。裸片尺寸为 5mm×6mm。点击查看大图。</div></p> <p>芯片的指令由中间的大型微码 ROM 定义。为了执行浮点指令,8087 对指令进行解码,然后微码引擎开始从微码 ROM 中执行相应的微指令。位于 ROM 右侧的微码解码电路从每条微指令生成相应的控制信号。<span id="fnref:decode"><a class="ref" href="#fn:decode">1</a></span> 总线寄存器和控制电路处理与主 8086 处理器以及系统其他部分的交互。</p> <h2>8087 的微码</h2> <p>执行一条 8087 指令(如 arctan)需要数百个内部步骤来计算结果。这些步骤通过微码实现,微指令指定算法的每一步。(请记住程序员使用的汇编语言指令与芯片内部使用的未公开的低级微指令之间的区别。)微码 ROM 包含 1648 条微指令,实现了 8087 的指令集。每条微指令长 16 位,执行一个简单操作,例如在芯片内部移动数据、相加两个值或移位数据。我正在与“Opcode Collective”合作,对微指令进行逆向工程,并完全理解微码(<a href="https://github.com/a-mcego/granite/blob/main/tools/8087mc/bin/8087.md">link</a>)。</p> <p>微码引擎(下图)控制微指令的执行,充当 8087 内部的微型 CPU。具体来说,它生成一个 11 位的微地址,即 ROM 中微指令的地址。微码引擎实现微码内的跳转、子程序调用和返回。这些跳转、子程序调用和返回都是有条件的;微码引擎将根据指定条件的值执行该操作或跳过它。</p> <p><a href="https://static.righto.com/images/8087-conditions/engine.jpg"><img alt="微码引擎。此图中金属层已被移除,显示下方的硅和多晶硅。" class="hilite" height="633" src="https://static.righto.com/images/8087-conditions/engine-w200.jpg" title="微码引擎。此图中金属层已被移除,显示下方的硅和多晶硅。" width="200" /></a><div class="cite">微码引擎。此图中金属层已被移除,显示下方的硅和多晶硅。</div></p> <p>我稍后会详细写微码引擎,但这里先给出一个概述。在最上方,指令解码 PLA<span id="fnref:pla"><a class="ref" href="#fn:pla">2</a></span> 对 8087 指令进行解码,以确定微码中的起始地址。在其下方,跳转 PLA 保存跳转和子程序调用的微码地址。再下方,六个 11 位寄存器实现微码栈,允许微码内最多六层的子程序调用。(注意,这个栈与保存八个浮点值的 8087 寄存器栈完全不同。)栈寄存器有相关的读写电路。增量器将微地址加一以顺序执行代码。引擎还通过加法器将偏移量加上当前位置,实现相对跳转。在最底部,地址锁存器和驱动器增强 11 位地址输出并将其发送到微码 ROM。</p> <h2>选择条件</h2> <p>一条微指令可以指定“如果寄存器为零则向前跳转 5 条微指令”,微码引擎将根据寄存器值执行跳转或忽略它。在电路中,条件使微码引擎执行跳转或阻止跳转。但硬件如何从大量条件中选择一个条件呢?</p> <p>微指令中的六位可以指定 64 种条件之一。类似于下面理想化图的电路会选择指定的条件。关键组件是一个多路复用器,下面用梯形表示。多路复用器是一种简单的电路,从四个输入中选择一个。通过将多路复用器排列成树状,左侧的 64 个条件之一被选中并成为输出,传递给微码引擎。</p> <p><a href="https://static.righto.com/images/8087-conditions/muxtree2.jpg"><img alt="多路复用器树选择其中一个条件。此图已简化。" class="hilite" height="414" src="https://static.righto.com/images/8087-conditions/muxtree2-w400.jpg" title="多路复用器树选择其中一个条件。此图已简化。" width="400" /></a><div class="cite">多路复用器树选择其中一个条件。此图已简化。</div></p> <p>例如,如果微码的 J 和 K 位是 00,最右边的多路复用器将选择第一个输入。如果 LM 位是 01,中间的多路复用器将选择第二个输入,如果 NO 位是 10,左边的多路复用器将选择其第三个输入。结果是条件 06 将通过树并成为输出。<span id="fnref:mux"><a class="ref" href="#fn:mux">3</a></span> 通过改变控制多路复用器的位,可以使用任何输入。(我们任意地将 16 个微码位命名为字母 A 到 P。)</p> <p>物理上,这些条件来自散布在裸片上的位置。例如,涉及操作码的条件来自 inst</p>
查看原文
查看缓存全文

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

# Intel 8087 浮点芯片微码中的条件 来源:http://www.righto.com/2025/12/8087-microcode-conditions.html 在 1980 年代,如果你想让计算机更快地进行浮点运算,可以购买 Intel 8087 浮点协处理器芯片。将它插入 IBM PC 后,运算速度最高可提升 100 倍,这对电子表格和其他数字密集型应用来说是巨大的提升。8087 使用复杂的算法来计算三角函数、对数和指数函数。这些算法通过芯片内部的微码实现。我参与了一个逆向工程该微码的小组。本文将探讨 8087 微码在其算法中使用的 49 种条件测试。有些条件很简单,比如检查一个数是否为零或负数;而另一些则很特殊,比如确定数字的舍入方向。 为了探究 8087 的电路,我打开了一个 8087 芯片,并用显微镜拍摄了大量的硅片照片。在硅片边缘,你可以看到连接芯片与 40 个外部引脚的细如发丝的键合线。硅片上的复杂图案由金属布线以及其下方的多晶硅和硅形成。芯片的下半部分是“数据通路”,负责对 80 位浮点值进行运算。数据通路的左侧是一个[常量 ROM](https://www.righto.com/2020/05/extracting-rom-constants-from-8087-math.html),存储着 π 等重要常量。右侧是程序员用来存放浮点值的八个寄存器;一个不寻常的设计决策是,这些寄存器被组织成一个[栈](https://www.righto.com/2025/12/8087-stack-circuitry.html)。 [Intel 8087 浮点单元芯片的硅片照片,标注了主要功能模块。硅片尺寸为 5mm×6mm。点击查看大图。](https://static.righto.com/images/8087-conditions/8087-die-labeled.jpg) Intel 8087 浮点单元芯片的硅片照片,标注了主要功能模块。硅片尺寸为 5mm×6mm。点击查看大图。 芯片的指令由中间的大型[微码 ROM](https://www.righto.com/2018/09/two-bits-per-transistor-high-density.html) 定义。为了执行一条浮点指令,8087 会解码该指令,然后微码引擎开始从微码 ROM 中执行相应的微指令。ROM 右侧的微码解码电路从每条微指令中生成适当的控制信号[1](http://www.righto.com/2025/12/8087-microcode-conditions.html#fn:decode)。总线寄存器和控制电路处理与主 8086 处理器及系统其他部分的交互。 执行一条 8087 指令(如 arctan)需要数百个内部步骤来计算结果。这些步骤通过微码实现,微指令指定算法的每一步。(请记住程序员使用的汇编语言指令与芯片内部使用的未记录的底层微指令之间的区别。)微码 ROM 包含 1648 条微指令,实现了 8087 的指令集。每条微指令长度为 16 位,执行简单的操作,例如在芯片内部移动数据、相加两个值或[移位](https://www.righto.com/2020/05/die-analysis-of-8087-math-coprocessors.html)数据。我与“Opcode Collective”小组合作,对微指令进行逆向工程,并完全理解微码([链接](https://github.com/a-mcego/granite/blob/main/tools/8087mc/bin/8087.md))。 微码引擎(下图)控制微指令的执行,充当 8087 内部的微型 CPU。具体来说,它生成一个 11 位的微地址,即 ROM 中微指令的地址。微码引擎实现微码内的跳转、子程序调用和返回。这些跳转、子程序调用和返回都是有条件的;微码引擎将根据指定条件的值来执行操作或跳过操作。 [微码引擎。在此图像中,金属层已被去除,显示了下方的硅和多晶硅。](https://static.righto.com/images/8087-conditions/engine.jpg) 微码引擎。在此图像中,金属层已被去除,显示了下方的硅和多晶硅。 我稍后会详细写关于微码引擎的内容,但这里先做一个概述。顶部是指令解码 PLA[2](http://www.righto.com/2025/12/8087-microcode-conditions.html#fn:pla),用于解码 8087 指令以确定微码中的起始地址。其下方是跳转 PLA,保存着跳转和子程序调用的微码地址。再往下,六个 11 位寄存器实现了微码堆栈,允许微码内进行六级子程序调用。(请注意,此堆栈与保存八个浮点值的 8087 寄存器堆栈完全不同。)堆栈寄存器有相关的读写电路。增量器将微地址加一以顺序执行代码。引擎还实现了相对跳转,使用加法器将偏移量加到当前位置。底部是地址锁存器和驱动电路,用于提升 11 位地址输出并将其发送到微码 ROM。 ## 选择条件 一条微指令可以表示为“如果寄存器为零,则向前跳转 5 条微指令”,微码引擎将根据寄存器值执行跳转或忽略它。在电路中,条件使微码引擎要么执行跳转,要么阻止跳转。但硬件如何从大量条件中选择一个条件呢? 微指令的六个位可以指定 64 个条件之一。类似于下面理想化示意图的电路会选择指定的条件。关键组件是一个多路复用器,用梯形表示。多路复用器是一种简单的电路,从四个输入中选择一个。通过将多路复用器排列成树状结构,左侧 64 个条件中的一个被选中并成为输出,传递给微码引擎。 [多路复用器树选择其中一个条件。此图已简化。](https://static.righto.com/images/8087-conditions/muxtree2.jpg) 多路复用器树选择其中一个条件。此图已简化。 例如,如果微码的 J 和 K 位为 00,最右边的多路复用器将选择第一个输入。如果 LM 位为 01,中间的多路复用器将选择第二个输入,而如果 NO 位为 10,左边的多路复用器将选择其第三个输入。结果是条件 06 将通过树结构并成为输出[3](http://www.righto.com/2025/12/8087-microcode-conditions.html#fn:mux)。通过改变控制多路复用器的位,可以使用任何输入。(我们任意将 16 个微码位命名为字母 A 到 P。) 物理上,条件来自硅片上分散的位置。例如,涉及操作码的条件来自芯片的指令解码部分,而涉及寄存器的条件则在寄存器旁边评估。为所有条件铺设 64 条导线到微码引擎效率低下。基于树的方法减少了布线,因为“叶子”多路复用器可以放置在相关条件电路的附近。因此,只有一条导线需要长距离传输,而不是多条导线。换句话说,条件选择电路分布在芯片各处,而不是作为一个集中的模块实现。 由于条件并不总是分为四组,实际的实现与上面的理想化示意图略有不同。特别是,顶层多路复用器有五个输入,而不是四个[4](http://www.righto.com/2025/12/8087-microcode-conditions.html#fn:inputs)。其他多路复用器并未使用全部四个输入。这样可以更好地匹配条件电路和多路复用器的物理位置。总的来说,8087 实现了可能的 64 个条件中的 49 个。 选择四个条件之一的电路称为多路复用器。它由传输门晶体管构成,这些晶体管被配置为要么让信号通过,要么阻断信号。为了操作多路复用器,一条选择线被激活,打开相应的传输门晶体管。这允许选中的输入通过晶体管到达输出,而其他输入被阻断。 [一个 4-1 多路复用器,由四个传输门晶体管构成。](https://static.righto.com/images/8087-conditions/multiplexer.jpg) 一个 4-1 多路复用器,由四个传输门晶体管构成。 下图展示了多路复用器在硅片上的样子。粉红色区域是掺杂硅。白线是多晶硅导线。当多晶硅跨越掺杂硅时,就形成了一个晶体管。左侧是一个四路多路复用器,由四个传输门晶体管构成。它接收四个条件(编号 38、39、3a、3b)的输入(黑色)。有四个控制信号(红色)对应于 N 和 O 位的四种组合。其中一个输入将通过晶体管到达输出,由活动的控制信号选择。右半部分包含逻辑电路(四个或非门和两个反相器),用于从微码位生成控制信号。(金属线从逻辑电路水平连接到控制信号触点,但我在拍摄这张照片时溶解了金属。)8087 中的每个多路复用器都有完全不同的布局,是根据信号位置和周围电路手动优化的。尽管多路复用器的电路是规则的(四个晶体管并联),但物理布局看起来有些杂乱。 [多路复用器在硅片上的样子。金属层已被去除,以显示多晶硅和硅。“扎染”图案是由于氧化层未完全去除而产生的薄膜效应。](https://static.righto.com/images/8087-conditions/mux-diagram.jpg) 多路复用器在硅片上的样子。金属层已被去除,以显示多晶硅和硅。“扎染”图案是由于氧化层未完全去除而产生的薄膜效应。 8087 在许多电路中使用传输门晶体管,而不仅仅是多路复用器。带有传输门晶体管的电路与常规逻辑门不同,因为传输门晶体管不提供放大。相反,信号在通过传输门晶体管时会变弱。为了解决这个问题,在条件树中插入了反相器或缓冲器来增强信号;上图中省略了它们。 ## 条件 在 8087 的 49 个不同条件中,有些在微码中广泛使用,而另一些则是为特定目的设计的,只使用一次。完整的条件集在脚注[7](http://www.righto.com/2025/12/8087-microcode-conditions.html#fn:conditions)中描述,但我会在这里重点介绍一些。 有十五个条件检查当前指令操作码的位。这允许一个微码例程处理一组相似的指令,然后根据具体指令改变行为。例如,条件测试指令是否为乘法,指令是否为 FILD/FIST(整数加载或存储),或者操作码的最低有效位是否被置位[5](http://www.righto.com/2025/12/8087-microcode-conditions.html#fn:instructions)。 8087 有三个临时寄存器——tmpA、tmpB 和 tmpC——用于在计算过程中保存值。各种条件检查 tmpA 和 tmpB 寄存器中的值[6](http://www.righto.com/2025/12/8087-microcode-conditions.html#fn:swap)。特别是,8087 使用一种有趣的内部存储数字方式:每个 80 位浮点值还有两个“标签”位。这些位对程序员来说基本不可见,可以视为元数据。标签位指示寄存器是空的、包含零、包含“正常”数字,还是包含特殊值,如 NaN(非数字)或无穷大。8087 使用标签位来优化操作。标签位还检测栈溢出(存储到非空栈寄存器)或栈下溢(从空栈寄存器读取)。 其他条件则高度专业化。例如,一个条件查看舍入模式设置和值的符号,以确定该值应该向上舍入还是向下舍入。其他条件处理异常,例如数字太小(即非规格化)或丢失精度。另一个条件测试两个值是否具有相同的符号。还有一个条件测试两个值是否具有相同的符号,但如果当前指令是减法,则将结果取反。最简单的条件就是“真”,允许无条件分支。 为了灵活性,条件可以被“翻转”,即当条件为真时跳转,或者当条件为假时跳转。这由微码的 P 位控制。在电路中,这是通过一个将 P 位与条件进行异或运算的门来实现的。结果是,如果 P 位被置位,条件的状态就会被翻转。 举一个使用条件的具体例子,考虑实现 `FCHS` 和 `FABS`(分别用于改变符号和计算绝对值的指令)的[微码例程](https://raw.githubusercontent.com/a-mcego/granite/refs/heads/main/tools/8087mc/bin/8087mc_out.txt#:~:text=%230896%09AB%20%20%20%20%20%20I%20%20L%20N%20%20%09c094%09%2Bjmp%2D%3E%230898%20cond%3D0x0a%20opcode%261)。这些操作几乎相同(切换符号位与清除符号位),因此同一个微码例程处理两条指令,通过一条跳转指令来处理差异。`FABS` 和 `FCHS` 指令被设计为具有相同的操作码,只是 `FABS` 的最低有效位被置位。因此,微码例程使用一个测试最低有效位的条件,允许例程根据 `FABS` 与 `FCHS` 进行分支并改变其行为。 查看相关的微指令,其十六进制值为 `0xc094`,二进制为 `110 000001 001010 0`。前三位 (ABC=110) 指定相对跳转操作(100 会跳转到固定目标,101 会执行子程序调用)。位 D 到 I (`000010`) 指示跳转量 (`+`)。位 J 到 O (`001010`,十六进制 0a) 指定要测试的条件,在此例中为指令操作码的最后一位。最后一位 (P) 如果被置位,则会翻转条件(即条件为假时跳转)。因此,对于 `FABS`,跳转指令将向前跳转一条微指令。这会跳过下一条微指令,而下一条微指令是为 `FCHS` 设置适当符号位的。 ## 结论 8087 通过使用专门为浮点运算优化的特殊硬件,比 8086 快得多地执行浮点操作。条件码电路就是其中一个例子:8087 可以在单个操作中测试一个复杂条件。然而,这些复杂的条件使得理解微码变得更加困难。但通过结合检查电路和分析微码,我们正在取得进展。感谢“Opcode Collective”小组的辛勤工作,特别是 Smartest Blob 和 Gloriouscow。 如需更新,请在 Bluesky (@righto.com)、Mastodon (@[email protected]) 或 RSS 上关注我。 ## 注释与参考

相似文章

Intel 8087浮点芯片的指令解码

Ken Shirriff

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