Intel 8087浮点芯片的堆栈电路逆向工程

Ken Shirriff 新闻

摘要

本文详细介绍了对Intel 8087浮点协处理器堆栈电路的逆向工程,解释了该芯片基于堆栈的寄存器架构和微码ROM如何实现快速浮点运算。

<p>早期的微处理器在处理浮点数时速度非常慢。但在1980年,Intel推出了8087浮点协处理器,其浮点运算速度最高可提升100倍。这对于AutoCAD、电子表格和飞行模拟器等IBM PC应用来说是一个巨大的好处。8087如此有效,以至于今天的计算机仍然使用基于8087的浮点系统。<span id="fnref:ieee-754"><a class="ref" href="#fn:ieee-754">1</a></span></p> <!-- It's hard to compute floating-point operations both quickly and accurately. Problems can arise from overflow, rounding, transcendental operations, and numerous edge cases. Prior to the 8087, each manufacturer had their own incompatible ad hoc implementation of floating point. Intel, however, enlisted numerical analysis expert [William Kahan](https://en.wikipedia.org/wiki/William_Kahan) to design accurate floating point based on rigorous principles. The 8087 has its problems, but it was a large improvement on earlier floating-point systems. The designers of the 8087 commented on the guidance offered by Professor Kahan: "We did not do as well as he wanted, but we did better than he expected." -- The 8087 Primer, page viii --> <p>8087在当时是一款极其复杂的芯片,根据来源不同,包含大约4万到7.5万个晶体管。<span id="fnref:count"><a class="ref" href="#fn:count">2</a></span> 为了探索8087的工作原理,我打开了一个芯片,并用显微镜拍摄了大量硅片照片。在芯片边缘,可以看到连接芯片到40个外部引脚的细如发丝的键合线。芯片上的复杂图案由金属布线以及下方的多晶硅和硅形成。芯片的下半部分是“数据路径”,即执行80位浮点数值计算的电路。在数据路径的左侧,一个<a href="https://www.righto.com/2020/05/extracting-rom-constants-from-8087-math.html">常量ROM</a>存储了重要的常数,如&pi;。右侧是构成堆栈的八个寄存器以及堆栈控制电路。</p> <p><a href="https://static.righto.com/images/8087-stack/8087-die-labeled.jpg"><img alt="Intel 8087浮点单元芯片的晶圆,标注了主要功能区块。晶圆尺寸为5mm&times;6mm。点击查看大图。" class="hilite" height="587" src="https://static.righto.com/images/8087-stack/8087-die-labeled-w450.jpg" title="Intel 8087浮点单元芯片的晶圆,标注了主要功能区块。晶圆尺寸为5mm&times;6mm。点击查看大图。" width="450" /></a><div class="cite">Intel 8087浮点单元芯片的晶圆,标注了主要功能区块。晶圆尺寸为5mm&times;6mm。点击查看大图。</div></p> <p>芯片的指令由中间的大型<a href="https://www.righto.com/2018/09/two-bits-per-transistor-high-density.html">微码ROM</a>定义。这个ROM非常不寻常;它是半模拟的,通过使用四种晶体管尺寸在每个晶体管中存储两个比特。为了执行一个浮点指令,8087对指令进行解码,然后微码引擎开始从微码ROM执行相应的微指令。ROM右侧的解码电路从每条微指令生成适当的控制信号。总线寄存器和控制电路处理与主8086处理器以及系统其余部分的交互。最后,<a href="https://www.righto.com/2018/08/inside-die-of-intels-8087-coprocessor.html">偏置发生器</a>使用电荷泵产生负电压来偏置芯片的衬底(底部的硅)。</p> <p>堆栈寄存器和控制电路(上图中红色部分)是本文的主题。与大多数处理器不同,8087将其寄存器组织成堆栈,指令在堆栈顶部进行操作。例如,平方根指令将堆栈顶部的值替换为其平方根。您还可以访问相对于堆栈顶部的寄存器,例如,将顶部值与堆栈向下两个位置的值相加。基于堆栈的架构旨在改进指令集、简化编译器设计并提高函数调用效率,尽管其效果不如预期。</p> <p><a href="https://static.righto.com/images/8087-stack/stack-diagram.jpg"><img alt="8087上的堆栈。来自《The 8087 Primer》第60页。" class="hilite" height="204" src="https://static.righto.com/images/8087-stack/stack-diagram-w350.jpg" title="8087上的堆栈。来自《The 8087 Primer》第60页。" width="350" /></a><div class="cite">8087上的堆栈。来自<i>The 8087 Primer</i>,第60页。</div></p> <p>上图显示了堆栈的工作原理。堆栈由八个寄存器组成,堆栈顶部 (ST) 指示当前堆栈的顶部。要将浮点数值压入堆栈,堆栈顶部递减,然后将值存储在新的顶部寄存器中。弹出操作通过从堆栈顶部复制值,然后递增堆栈顶部来完成。相比之下,大多数处理器直接指定寄存器,因此寄存器2始终是同一个寄存器。</p> <h2>寄存器</h2> <p>堆栈寄存器在8087的晶圆上占据了相当大的面积,因为浮点数占用很多比特。浮点数由小数部分(有时称为尾数或有效数)以及指数部分组成;指数使浮点数能够覆盖从极小到极大的范围。在8087中,浮点数为80位:64位有效数、15位指数和1位符号位。在8位或16位计算机时代,80位寄存器非常大;8087中的八个寄存器相当于8086处理器中的40个寄存器。</p> <p><a href="https://static.righto.com/images/8087-stack/registers.jpg"><img alt="8087中的寄存器形成一个8&times;80的单元格网格。特写显示一个8&times;8的方块。我用酸去除了金属层,以显示下面的硅电路。" class="hilite" height="684" src="https://static.righto.com/images/8087-stack/registers-w500.jpg" title="8087中的寄存器形成一个8&times;80的单元格网格。特写显示一个8&times;8的方块。我用酸去除了金属层,以显示下面的硅电路。" width="500" /></a><div class="cite">8087中的寄存器形成一个8&times;80的单元格网格。特写显示一个8&times;8的方块。我用酸去除了金属层,以显示下面的硅电路。</div></p> <p>寄存器将每个比特存储在一个静态RAM单元中。每个单元有两个反相器连接成一个环路。该电路形成一个稳定的反馈回路,一个反相器开启,另一个关闭。根据哪个反相器开启,电路存储0或1。要向电路写入新值,其中一条线被拉低,将环路翻转到所需状态。诀窍在于每个反相器使用一个非常弱的晶体管来拉高输出,因此其输出很容易被覆盖以改变状态。</p> <p><a href="https://static.righto.com/images/8087-stack/inverter-loop.png"><img alt="两个反相器组成的环路可以存储0或1。" class="hilite" height="121" src="https://static.righto.com/images/8087-stack/inverter-loop-w250.png" title="两个反相器组成的环路可以存储0或1。" width="250" /></a><div class="cite">两个反相器组成的环路可以存储0或1。</div></p> <p>这些反相器对排列成一个8×80的网格,实现了8个80位的字。每80行中有两条<em>位线</em>提供对位的访问。位线提供对位的读写访问;一对位线允许任一反相器被拉低以存储所需的位值。八条垂直的<em>字线</em>使能访问</p>
查看原文
查看缓存全文

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

# Intel 8087 浮点芯片的堆栈电路逆向工程 来源:http://www.righto.com/2025/12/8087-stack-circuitry.html 早期的微处理器处理浮点数时速度非常慢。但在 1980 年,Intel 推出了 8087 浮点协处理器,其浮点运算速度最高提升了 100 倍。这对 IBM PC 应用程序(如 AutoCAD、电子表格和飞行模拟器)来说是一个巨大的优势。8087 如此高效,以至于今天的计算机仍在使用基于 8087 的浮点系统。^1 (http://www.righto.com/2025/12/8087-stack-circuitry.html#fn:ieee-754) 8087 在当时是一颗极其复杂的芯片,不同资料显示其晶体管数量在 40,000 到 75,000 之间。^2 (http://www.righto.com/2025/12/8087-stack-circuitry.html#fn:count)为了探索 8087 的工作原理,我打开了一颗芯片,并用显微镜拍摄了大量硅片的照片。在硅片边缘,可以看到连接芯片与 40 个外部引脚的发丝般细的键合线。硅片上复杂的图案由金属布线以及下方的多晶硅和硅构成。芯片的下半部分是“数据路径”(datapath),即对 80 位浮点数值进行运算的电路。在数据路径的左侧,一个[常量 ROM](https://www.righto.com/2020/05/extracting-rom-constants-from-8087-math.html) 存储了 π 等重要常量。右侧是构成堆栈的八个寄存器,以及堆栈控制电路。 Intel 8087 浮点单元芯片的硅片图,标注了主要功能模块。硅片尺寸为 5mm×6mm。点击查看大图。(https://static.righto.com/images/8087-stack/8087-die-labeled.jpg) Intel 8087 浮点单元芯片的硅片图,标注了主要功能模块。硅片尺寸为 5mm×6mm。点击查看大图。 芯片的指令由位于中间的大型[微码 ROM](https://www.righto.com/2018/09/two-bits-per-transistor-high-density.html) 定义。这个 ROM 非常特殊,它是一种半模拟结构,通过使用四种晶体管尺寸,每个晶体管存储两个比特。为了执行一条浮点指令,8087 会对指令进行译码,然后微码引擎开始从微码 ROM 中执行相应的微指令。ROM 右侧的译码电路为每条微指令生成相应的控制信号。总线寄存器和控制电路负责处理与主 8086 处理器以及系统其余部分的交互。最后,[偏置发生器](https://www.righto.com/2018/08/inside-die-of-intels-8087-coprocessor.html) 使用电荷泵产生负电压,为芯片的衬底(底层硅)提供偏置。 堆栈寄存器及其控制电路(上图中红色部分)是本文的主题。与大多数处理器不同,8087 将其寄存器组织成一个堆栈,指令在堆栈顶部进行操作。例如,平方根指令会将堆栈顶部的值替换为其平方根。您还可以访问相对于堆栈顶部的寄存器,例如,将顶部值与顶部往下两个位置的值相加。这种基于堆栈的架构旨在改进指令集、简化编译器设计并使函数调用更高效,尽管实际效果并未达到预期。 8087 上的堆栈。摘自 *The 8087 Primer*,第 60 页。(https://static.righto.com/images/8087-stack/stack-diagram.jpg) 8087 上的堆栈。摘自 *The 8087 Primer*,第 60 页。 上图展示了堆栈的工作方式。堆栈由八个寄存器组成,堆栈顶部指针 ST 指示当前栈顶。要将一个浮点数值压入堆栈,栈顶指针递减,然后将数值存储在新的顶部寄存器中。弹出操作则是从栈顶复制数值,然后栈顶指针递增。相比之下,大多数处理器直接指定寄存器,因此寄存器 2 始终是同一个寄存器。 ## 寄存器 堆栈寄存器在 8087 的硅片上占据了相当大的面积,因为浮点数需要很多比特位。一个浮点数由小数部分(有时称为尾数或有效数字)和指数部分组成;指数使得浮点数能够涵盖从极小到极大的范围。在 8087 中,浮点数为 80 位:64 位有效数字、15 位指数和 1 位符号位。在 8 位或 16 位计算机时代,一个 80 位寄存器是非常大的;8087 中的八个寄存器相当于 8086 处理器中的 40 个寄存器。 8087 中的寄存器构成一个 8×80 的单元网格。特写图显示了一个 8×8 的区块。我使用酸液去除了金属层,以显露下方的硅电路。(https://static.righto.com/images/8087-stack/registers.jpg) 8087 中的寄存器构成一个 8×80 的单元网格。特写图显示了一个 8×8 的区块。我使用酸液去除了金属层,以显露下方的硅电路。 每个比特位存储在一个静态 RAM 单元中。每个单元有两个反向器连接成环路。这个电路形成一个稳定的反馈环,其中一个反向器导通,另一个截止。根据哪个反向器导通,电路存储 0 或 1。要向电路写入新值,其中一条线被拉低,使环路翻转至所需状态。关键在于,每个反向器使用一个非常弱的晶体管来拉高输出,因此其输出很容易被压倒而改变状态。 两个反向器组成的环路可以存储 0 或 1。(https://static.righto.com/images/8087-stack/inverter-loop.png) 两个反向器组成的环路可以存储 0 或 1。 这些反向器对排列成一个 8×80 的网格,实现了 8 个 80 位的字。80 行中的每一行都有两条*位线*用于访问一个比特位。位线提供对某个比特位的读写访问;这两条位线允许任意一个反向器被拉低,以存储所需的比特值。八条垂直的*字线*用于访问一个字(80 位的列)。每条字线会导通 160 个传输晶体管,将位线与选中列的反向器连接起来。因此,当字线被使能时,位线可用于读取或写入该字。 尽管芯片看起来是二维的,但实际上它由多个层构成。底层是硅。下方粉红色的区域是经过“掺杂”以改变其电学特性的硅,从而成为电路的有源部分。掺杂硅形成了水平和垂直的导线网格,中间有较大的掺杂区域。在硅的上面,多晶硅布线提供了两个功能。首先,它提供了一层用于连接电路的布线。但更重要的是,当多晶硅跨越掺杂硅时,会形成一个晶体管。多晶硅提供栅极,用于控制晶体管的导通与关断。在这张照片中,多晶硅几乎不可见,因此我将其一部分用红色标出。最后,水平的金属导线提供了第三层互连布线。通常,金属层会遮盖下方的电路,因此我在这张照片中用酸去除了金属层。我画了蓝色线条来表示金属层。触点用于连接各个层。 寄存器中一个存储单元的放大图。金属层和大部分多晶硅已被去除,以显示下方的硅。(https://static.righto.com/images/8087-stack/memory-cell-layers.jpg) 寄存器中一个存储单元的放大图。金属层和大部分多晶硅已被去除,以显示下方的硅。 这些层组合起来形成了存储单元的反向器和选择晶体管,虚线框内所示。共有六个晶体管(黄色),位于多晶硅跨越掺杂硅的位置。每个反向器有一个将输出拉低的晶体管,以及一个将输出拉高的弱晶体管。当字线(垂直多晶硅)有效时,它通过两个选择晶体管将选中的反向器连接到位线(水平金属)。这使得该比特位可以被读取或写入。 存储单元中电路的功能。(https://static.righto.com/images/8087-stack/memory-cell-labeled.jpg) 存储单元中电路的功能。 每个寄存器关联有两个标签位,这是一种不常见的元数据形式,用于指示寄存器是空的、包含零、包含有效值,还是包含特殊值(如无穷大)。标签位用于内部优化性能,对程序员来说基本无关紧要。除了随寄存器一起访问外,标签位还可以作为 16 位的“标签字”并行访问。这使得标签可以作为 8087 状态的一部分保存或加载,例如在中断处理期间。 ## 译码器 译码电路嵌在寄存器文件的中间,用于选择一个寄存器。寄存器内部通过一个 3 位值来指定。译码电路根据该值使能八条寄存器选择线中的一条。 译码电路很简单:它有八个 3 输入 NOR 门来匹配八种位模式之一。然后通过一个使用大晶体管的高电流驱动器为选择线供电。(在下面的照片中,您可以将大的蛇形驱动器晶体管与位单元中的小晶体管进行比较。) 译码电路有八个类似的模块来驱动八条选择线。(https://static.righto.com/images/8087-stack/decoder.jpg) 译码电路有八个类似的模块来驱动八条选择线。 译码器有一个有趣的电气优化。如前所述,寄存器选择线是八条垂直运行的多晶硅线,贯穿整个寄存器文件。不幸的是,多晶硅的电阻相当高,比硅好但远不如金属。问题是,一条长多晶硅线的电阻会拖慢系统速度。也就是说,晶体管栅极的电容与高电阻相结合,会导致信号产生 RC(阻容)延迟。 解决方案是,寄存器选择线也运行在金属层中,这是一组紧邻寄存器文件右侧的线。这些线从寄存器文件下方约三分之一处分支出来,延伸到底部,然后在底部重新连接回多晶硅选择线。这降低了选择线的最大电阻,从而提高了速度。 示意图显示 8 条金属线与主选择线平行运行。寄存器文件的实际高度比图示要高得多;中间部分已被移除以便图示适配。(https://static.righto.com/images/8087-stack/select.jpg) 示意图显示 8 条金属线与主选择线平行运行。寄存器文件的实际高度比图示要高得多;中间部分已被移除以便图示适配。 ## 堆栈控制电路 堆栈比常规寄存器文件需要更多的控制电路,因为电路必须跟踪栈顶的位置。^3 (http://www.righto.com/2025/12/8087-stack-circuitry.html#fn:status-word) 控制电路在压栈或弹栈时递增和递减栈顶指针(紫色)。^4 (http://www.righto.com/2025/12/8087-stack-circuitry.html#fn:patents) 此外,8087 指令可以根据偏移量访问一个寄存器,例如从栈顶算起的第三个寄存器。为了支持这一点,控制电路可以临时将偏移量加到栈顶位置上(绿色)。多路选择器(红色)选择栈顶值或加法器的输出,并将其馈送到译码器(蓝色),译码器如前所述在寄存器文件(黄色)中选择八个堆栈寄存器之一。 8087 中的寄存器堆栈。改编自美国专利 USRE33629E。我不确定 GRX 字段是什么。我也不明白为什么这里显示的是减法器而不是加法器。(https://static.righto.com/images/8087-stack/patent-diagram.jpg) 8087 中的寄存器堆栈。改编自[美国专利 USRE33629E](https://patents.google.com/patent/USRE33629E)。我不确定 GRX 字段是什么。我也不明白为什么这里显示的是减法器而不是加法器。 堆栈电路的物理实现如下所示。顶部的逻辑基于 16 位微指令选择堆栈操作。^5 (http://www.righto.com/2025/12/8087-stack-circuitry.html#fn:microcode) 下面是保存栈顶值的三个锁存器。(那些大的白色方块看起来很重要,但它们只是从地线到电路的“跨接线”,从金属导线下方穿过。) 堆栈控制电路。右侧的蓝色区域是我溶解 5V 电源的金属导轨时残留的氧化物。(https://static.righto.com/images/8087-stack/stack-circuitry.jpg) 堆栈控制电路。右侧的蓝色区域是我溶解 5V 电源的金属导轨时残留的氧化物。 三位加法器位于底部,旁边是多路选择器。您可能认为加法器会使用简单的“全加器”电路。但实际上,它是一个更快的[超前进位加法器](https://en.wikipedia.org/wiki/Carry-lookahead_adder)。这里不再赘述,但总结一下:在每个比特位上,一个与门产生进位生成信号,一个异或门产生进位传播信号。逻辑门将这些信号组合起来并行产生输出位,从而避免了进位逐位传播导致的延迟。 增量/减量器采用了一种完全不同的方法。三个比特位各使用一个触发器。少量逻辑门决定每个比特位是否应该翻转或保持之前的值。例如,在递增时,如果低位是 11(比如从 011 递增到 100),则高位翻转。在递减时,如果低位是 00(比如从 100 递减到 011),则高位翻转。更简单的逻辑决定中间位是否翻转。最低位更容易,每次递增或递减时都翻转。 下面的原理图显示了堆栈一个比特位的电路。每个比特位由一个中等复杂度的触发器实现,该触发器可以根据微码的控制信号进行清除、加载值或翻转操作。该触发器由两个置位-复位 (SR) 锁存器构成。注意,触发器的输出在反馈到输入时进行了交叉,从而提供了翻转操作所需的取反。在右侧,多路选择器选择寄存器值或加法器(未显示)的和,生成送至译码器的信号。 堆栈一个比特位的原理图。(https://static.righto.com/images/8087-stack/stack-schematic.jpg) 堆栈一个比特位的原理图。 ## 堆栈方法的缺点 根据 8087 的设计者的说法,^7 (http://www.righto.com/2025/12/8087-stack-circuitry.html#fn:references) 使用堆栈而非平面寄存器组的主要动机是指令没有足够的比特位来寻址多个寄存器操作数。此外,堆栈“在表达式解析和嵌套函数调用方面相比通用寄存器具有优势”。也就是说,堆栈适用于数学表达式,因为子表达式可以在栈顶求值。而对于函数调用,可以避免将寄存器保存到内存的开销,因为子程序可以使用堆栈而不干扰下面的值。至少这是当时的想法。 主要问题是“堆栈溢出”。8087 的堆栈有八个条目,因此如果你向堆栈中压入第九个值,堆栈就会溢出。

相似文章

Intel 8087浮点芯片的指令解码

Ken Shirriff

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