The Dawn of Nvidia's Technology
Nvidia 技术初 Dawn
由于 Nvidia 成为世界上最有价值的公司之一,现在有两本书解释了它的崛起,并赞扬了 Jensen Huang 的天才,分别是 Tae Kim 的 The Nvidia Way: Jensen Huang and the making of a tech giant 和 Steven Witt 的 The Thinking Machine: Jensen Huang, Nvidia, and the World's Most Coveted Microchip。对于历史的后 90%,我不在场,所以我不评论他们对那部分的处理。但对于 Sun Microsystems 的史前时期和历史的最初 10%,我都在场。Kim 对这个时代商业方面的描述非常详细,虽然那是三十年前的事了,但与我的记忆相符。
Witt 对早期历史商业方面的描述远不如 Kim 详细,而且一些细节与我的记忆不符。但就早期历史的技术方面而言,似乎两位作者都没有真正理解我们所做的两种创新的原因:成像模型和 I/O 架构。Witt 写道(Page 31):
第一次我问 Priem 关于 NV1 的架构时,他滔滔不绝地讲了 27 分钟。
在下面,我将尝试向那些 27 分钟里 Curtis 所谈论的内容进行解释。这需要我写一篇很长的帖子。
背景
基于 NV1 的 Diamond Edge Swaaye, CC-By-SA 3.0
在 Engineering For The Long Term 的 "Three Decades" 部分,我写道:
当我们开始 Nvidia 时,我们看到的机遇是 PC 正在从 PC/AT 总线过渡到 PCI 总线的版本 1。PC/AT 总线的带宽完全不足以支持 3D 游戏,但 PCI 总线具有更大的带宽。这是否足够是一个悬而未决的问题。我们显然需要尽可能地利用我们能够获得的有限带宽。
我们有两种基本方法来“尽可能地利用有限的带宽”:
- 减少给定图像需要在总线上发送的数据量。
- 增加总线每个周期发送的数据量。
成像模型
三角形是曲面最简单的描述。因此,几乎整个 3D 计算机图形的历史都使用三角形对 3D 对象的表面进行建模。但是,有一种技术,至少可以追溯到 Robert Mahl 1972 年的论文 Visible Surface Algorithms for Quadric Patches,用于直接对曲面建模。描述二次面片需要比三角形更多的数据。但是,要实现等效的真实感,您需要的面片要少得多,因此每帧的数据量会显着减少。
NV1 上的 Virtua Fighter
据我所知,当时只有 Sega 在视频游戏行业中使用二次面片。当我们在 Comdex 上推出 NV1 时,我们能够在 PC 上以全帧速率展示 Sega 街机游戏,例如 Virtua Fighter,这在行业中尚属首次。原因是 NV1 使用二次面片,从而更好地利用了有限的 PCI 总线带宽。
在 Sun,James Gosling 和我构建了极其复杂和前瞻性的但专有的 NeWS 窗口系统。与此同时,我还与 Digital Equipment 等竞争对手的工程师合作构建了 X Window System。我在 Sun 的许多学习经历之一发生在 X Window System 的漫长历史 的早期。我很快意识到 NeWS 无法与更简单、开源的 X 竞争。我主张 Sun 开源 NeWS,但失败了。我主张 Sun 放弃 NeWS 并采用 X,因为这是应用程序开发人员想要的。Sun 浪费了宝贵的时间,无法决定该怎么做,最终决定不做出决定,并浪费了大量资源将 NeWS 和 X 合并成一个蹩脚的方案,它比其前身更糟糕的 NeWS 和更糟糕的 X。这只是我在 Sun 输掉的众多战斗之一(这讨论了另一个)。
一旦 Microsoft 宣布 Direct X,我立即意识到,如果下一个芯片做二次面片,Nvidia 注定要失败,因为开发人员将不得不使用 Direct X 的三角形。但是,像 Sun 一样,Nvidia 似乎无法决定放弃其珍视的技术。做出有效决定的时间正在流逝。我辞职了,希望能够改变现状,从而能够做出做三角形的决定。它一定奏效了。书中讲述了 RIVA 128 出货时 Nvidia 离破产有多近。其余的都是历史,我只是一个观察者。
I/O 架构
相比之下,I/O 架构随着时间的推移取得了我们计划的巨大成功。Kim 写道(Page 95):
早期,Curtis Priem 发明了一种“虚拟化对象”架构,该架构将被纳入 Nvidia 的所有芯片中。一旦 Nvidia 采用更快的芯片发布节奏,它就成为该公司更大的优势。Priem 的设计具有基于软件的“资源管理器”,本质上是一个位于硬件本身之上的微型操作系统。资源管理器允许 Nvidia 的工程师模拟通常需要物理打印到芯片电路上的某些硬件功能。这涉及性能成本,但加快了创新步伐,因为 Nvidia 的工程师可以承担更多风险。如果新功能尚未准备好在硬件中工作,Nvidia 可以在软件中模拟它。与此同时,当有足够的剩余计算能力时,工程师可以取出硬件功能,从而节省芯片面积。对于 Nvidia 的大多数竞争对手来说,如果芯片上的硬件功能尚未准备好,这将意味着进度表延迟。但是,这在 Nvidia 不会发生,这要归功于 Priem 的创新。“这是地球上最棒的事情,”Michael Hara 说。“这是我们的秘密武器。如果我们错过了一个功能或一个功能坏了,我们可以把它放在资源管理器中,它就会工作。”Nvidia 的销售主管 Jeff Fisher 同意:“Priem 的架构对于使 Nvidia 能够更快地设计和制造新产品至关重要。”
背景
Nvidia 只是 Sun Microsystems 催生的众多创业公司之一。但在当时,Nvidia 在竞争对手的图形创业公司中独树一帜的原因在于,它拥有一支来自 Sun 团队的早期工程师,该团队构建了 GX 系列图形芯片。我们接受了强化教育,学习了在 Unix(一个多进程、虚拟内存操作系统)中有效实现图形所需的技术。竞争对手都来自 Windows 背景,当时是一个单进程、非虚拟内存系统。我们理解,在可预见的未来,Windows 将不得不发展多进程和虚拟内存。因此,向 VC 的宣传是,我们将设计一种“面向未来”的架构,并为 PC 的未来操作系统提供一种 Unix 图形芯片。GX 团队还从 Sun 外围设备的运输困难中吸取了教训,在 Sun,软件和硬件的进度表是不可分割的,因为 OS 驱动程序和应用程序需要详细了解物理硬件。这导致了“发射台鸡”,因为双方都试图将进度表延迟归咎于对方。
主要是写
以下是我们在 US5918050A : Apparatus accessed at a physical I/O address for address and data translation and for context switching of I/O devices in response to commands from application programs (发明人 David S. H. Rosenthal 和 Curtis Priem) 中解释这个问题的方式,使用速记“PDP11 架构”来表示 I/O 寄存器映射到与系统内存相同的地址空间的系统:
不仅输入/输出操作必须由操作系统软件执行,而且利用 PDP11 架构的计算机的设计通常要求中央处理单元读取每个输入/输出设备上的寄存器,以便完成任何输入/输出操作。由于中央处理单元已经变得更快,以便加速 PDP11 类型系统,因此有必要缓冲输入/输出总线上的写入操作,因为总线无法跟上中央处理单元的速度。因此,每个写入操作都由中央处理单元传输到缓冲区,在缓冲区中排队,直到可以处理它为止;中央处理单元和输入/输出设备之间的线路中的其他缓冲区的功能类似。在可以发生读取操作之前,必须按顺序执行所有排队的写入操作来刷新所有这些写入缓冲区,以便保持正确的操作顺序。因此,希望读取输入/输出设备上的寄存器中的数据的中央处理单元必须等到所有写入缓冲区都已刷新,然后才能访问总线以完成读取操作。典型系统在发生读取操作时,平均在其队列中有八个写入操作,并且必须处理所有这些写入操作,然后才能处理读取操作。这使得读取操作比写入操作慢得多。由于中央处理单元需要执行的许多操作与图形有关,需要读取帧缓冲区中大量像素,然后转换这些像素,最后将它们重写到新位置,因此图形操作变得非常慢。事实上,现代图形操作是第一个暴露 PDP11 架构的这个致命弱点的操作。
我们采取了两种方法来避免阻止 CPU。首先,我们在设备中实现了一个队列,一个相当长的 FIFO(先进先出),我们允许 CPU 从 FIFO 中读取空闲插槽的数量,它可以执行的写入次数,并保证不会被阻止。当 CPU 想要写入 NV1 时,它会询问 FIFO 它可以执行多少次写入。如果答案是 N,它会在再次询问之前执行 N 次写入。NV1 会立即确认每个写入,允许 CPU 继续计算下一次写入的数据。这是 US5805930A 的主题:System for FIFO informing the availability of stages to store commands which include data and virtual address sent directly from application programs(发明人 David S. H. Rosenthal 和 Curtis Priem),这是我们于 1995 年 5 月 15 日提交的申请的延续。请注意,这意味着应用程序不需要知道设备 FIFO 的大小。如果未来的芯片具有更大或更小的 FIFO,则未更改的应用程序将正确使用它。
其次,我们尽可能不使用 CPU 将数据传输到 NV1 和从 NV1 传输数据。相反,只要有可能,我们就使用直接内存访问,其中 I/O 设备独立于 CPU 读取和写入系统内存。在大多数情况下,CPU 指示 NV1 执行一些操作,只需写入一次或几次,然后继续执行其程序。该指令通常说“这里在内存中有一个二次面片块供您渲染”。如果 CPU 需要答案,它会告诉 NV1 将其放置在系统内存中的哪个位置,并定期检查它是否已到达。
请记住,我们正在为虚拟内存系统创建此架构,其中应用程序可以直接访问 I/O 设备。应用程序以虚拟地址寻址系统内存。系统的内存管理单元 (MMU) 将这些虚拟地址转换为总线使用的物理地址。当应用程序告诉设备面片块的地址时,它只能向设备发送其 虚拟 地址之一。为了从系统内存中获取面片,设备上的 DMA 引擎需要以与 CPU 的 MMU 相同的方式将虚拟地址转换为总线上的 物理 地址。因此,NV1 不仅有一个 DMA 引擎,还有一个 IOMMU。我们将此 IOMMU 申请了专利,专利号为 US5758182A: DMA controller translates virtual I/O device address received directly from application program command to physical i/o device address of I/O device on device bus (发明人 David S. H. Rosenthal 和 Curtis Priem)。在 2014 年的 Hardware I/O Virtualization 中,我解释了 Amazon 如何最终构建带有 IOMMU 的网络接口,用于 AWS 数据中心中的服务器,以便多个虚拟机可以直接访问网络硬件,从而消除操作系统开销。
上下文切换
对于 Unix(以及后来的 Linux、Windows、MacOS 等)等多进程操作系统中的图形支持,根本问题是为多个进程提供每个进程都可以独占访问单个图形设备的错觉。我于 1983 年在 Carnegie-Mellon 开始解决这个问题。James Gosling 和我构建了 Andrew Window System,该系统允许多个进程共享对屏幕的访问,每个进程都在自己的窗口中。但是他们无法访问真正的硬件。有一个访问真正硬件的单个服务器进程。应用程序向该服务器发出远程过程调用 (RPC),该服务器实际上绘制了请求的图形。四十年后,X Window System 仍然以这种方式工作。RPC 施加的性能损失使 3D 游戏无法使用。例如,为了允许一个游戏在一个窗口中运行,而一个邮件程序在另一个窗口中运行,我们需要当前活动的进程可以直接访问硬件,并且如果操作系统上下文切换到不同的图形进程,则授予该进程对硬件的直接访问权限。操作系统需要从图形硬件保存第一个进程的状态,并恢复第二个进程的状态。我们在 Sun 上对这个问题的研究导致了一项于 1989 年提交的专利,US5127098A: Method and apparatus for the context switching of devices(发明人 David S. H. Rosenthal、Robert Rocchetti、Curtis Priem 和 Chris Malachowsky)。这个想法是将设备映射到每个进程的内存中,但使用系统的内存管理单元 (MMU) 确保在任何时候,除了一个映射之外,所有映射都是 无效的。进程对无效映射的访问将中断到系统的页面错误处理程序中,该处理程序将调用设备的驱动程序来保存旧进程的上下文并恢复新进程的上下文。这个想法的一般问题是,由于中断最终进入页面错误处理程序中,因此它需要页面错误处理程序中的设备相关代码。这正是导致 Sun 出现进度表问题的软件和硬件之间的联系。
这个想法存在两个具体的 Nvidia 问题。首先,Windows 不是虚拟内存操作系统,因此您无法执行任何操作。其次,即使 Windows 已经发展成为虚拟内存操作系统,Microsoft 也不太可能让我们修改页面错误处理程序。
正如您在 '930 专利 的图 6 中所看到的,I/O 架构由 PCI 总线和可以实现多种不同 I/O 设备的内部总线之间的接口组成。该接口提供了许多功能:
- 它实现了 FIFO,在内部总线上的所有设备之间共享它。
- 它实现了 DMA 引擎及其 IOMMU,在内部总线上的所有设备之间共享它。
- 使用转换表,它允许应用程序使用虚拟名称通过接口连接到内部总线上的特定设备。
- 它确保一次只有一个应用程序可以访问该接口。
PCI 和 PC/AT 总线之间的区别不仅在于数据路径从 16 位增加到 32 位,还在于地址总线从 24 位增加到 32 位。地址空间大了 256 倍,因此 Nvidia 的设备可以占用更多的地址空间。我们可以实现许多虚拟 FIFO,以便每个应用程序都可以 有效 映射到其中一个。设备,而不是操作系统,将确保只有一个虚拟 FIFO 映射到单个物理 FIFO。访问未映射到物理 FIFO 的虚拟 FIFO 的进程将导致中断,但这次中断将转到设备的驱动程序,而不是页面错误处理程序。驱动程序可以执行上下文切换,并将物理 FIFO 重新分配给新的虚拟 FIFO。它还必须将页面表条目从 CPU 的 MMU 复制到 IOMMU 中,以反映新进程的页面在物理内存中的位置。不会有页面错误,因此操作系统页面错误处理程序中不会有设备的知识。正如我们在 '050 专利 中写道:
使用许多大小相同的输入/输出设备地址空间,每个地址空间仅分配给一个应用程序使用,这允许利用输入/输出地址来确定哪个应用程序启动了任何特定的输入/输出写入操作。
因为应用程序各自看到它们自己的虚拟 FIFO,所以未来的芯片可以实现多个物理 FIFO,允许多个进程的虚拟 FIFO 被分配一个物理 FIFO,这将减少上下文切换的需要。
对象和方法
NeWS 的一大优点是它使用 PostScript 编程。我们已经弄清楚如何使 PostScript 面向对象,与 SmallTalk 同构。我们在一个带有继承的类层次结构中组织了窗口系统中的对象。例如,这允许 Don Hopkins 以这样一种方式为 NeWS 实现 饼图菜单,即任何用户都可以用饼图菜单替换传统的矩形菜单。这太有趣了,以至于 Owen Densmore 和我使用了相同的技术来 为 Unix shell 实现面向对象编程。
在 PC 内存最多为 640 兆字节的时候,PCI 总线可以寻址 4 千兆字节的事实意味着它的许多地址位都是多余的。因此,我们决定通过使用其中的一些作为数据来增加每个总线周期发送的数据量。如果我没记错的话,NV1 使用了 23 个地址位,占总空间的 1/512。23 个地址位中的 7 个选择了 128 个虚拟 FIFO 之一,允许 128 个不同的进程共享对硬件的访问。我们认为 128 个进程就足够了。
剩余的 16 个地址位可以用作数据。从理论上讲,FIFO 可以是 48 位宽,32 位来自总线上的数据线,16 位来自地址线,每个总线周期的位数增加了 50%。NV1 忽略了地址的字节部分,因此 FIFO 只有 46 位宽。
因此,我们在一个类层次结构中组织了我们的 I/O 架构中的对象,根类是 CLASS。应用程序要做的第一件事是在代表 CLASS 类的对象上调用 enumerate()
方法。这将返回一个类 CLASS 的所有实例的名称列表,即此架构实例实现的所有对象类型。通过这种方式,设备的功能不会硬连接到应用程序中。应用程序会询问设备它的功能是什么。反过来,应用程序可以在列表中的 CLASS 类的每个实例上调用 enumerate()
,这将使应用程序获得每个类的每个实例的名称列表,可能是 LINE-DRAWER
。因此,应用程序将找到而不是_先验地_知道设备支持的所有不同类型的所有资源的名称(虚拟对象)。
然后,应用程序可以通过使用新创建的对象的 32 位名称调用类对象上的 instantiate()
方法来创建对象,即这些类的实例。因此,该接口仅限于每个应用程序的 4B 个对象。然后,应用程序可以 select()
命名对象,如果在转换表中没有该对象的条目,则会导致中断,以便资源管理器可以创建一个对象。每个 FIFO 的 64Kbyte 地址空间被划分为 8 个 8K“子区域”。应用程序可以在每个子区域中 select()
一个对象,因此它可以一次操作 8 个对象。随后对每个子区域的写入都被解释为对所选对象的方法调用,8Kbyte 空间中每个子区域的基址的字偏移量指定了方法,数据是该方法的参数。因此,该接口支持每个对象 2048 种不同的方法。