当我们谈论计算机硬件的复杂性时,现代 GPU 和专用显示芯片往往首当其冲。然而,回顾上世纪八十年代的 VGA(Video Graphics Array)适配器,一个看似简单却让无数模拟器开发者和驱动工程师头疼的问题始终存在:帧缓冲区内存访问机制并非表面上看起来那样直观。最近 OS/2 Museum 发布的文章深入探讨了这一话题,揭示了 VGA 内存访问之所以复杂的根本原因。
VGA 不是复杂硬件,却造成了复杂问题
VGA 硬件本身并不算复杂。它没有微代码,没有内置 CPU 或微控制器,执行的功能也相对简单。然而,VGA 由几个在逻辑上相互独立的组件构成,这些组件协同工作时产生的复杂度远超人们的预期。问题的根源在于文档资料的严重不足,这种困境从 IBM EGA(Enhanced Graphics Adapter)时代就已经开始,因为 VGA 不过是 EGA 的一个微小扩展版本。
EGA 的设计目标是在 1984 年的标准下支持高分辨率图形显示(最多 16 色),同时提供可编程字体,还要保持与 MDA/CGA 文本模式以及 CGA 图形模式的高度兼容性。但 EGA 并不是早期 IBM 适配器的寄存器兼容产品,其内部架构也大相径庭。如果直接用针对 CGA 图形模式编写的代码在 EGA 上运行,必然会失败。只有通过 BIOS 来建立模式,然后按照 CGA 的方式访问视频内存,才能正常工作。
EGA 足够古老,以至于它不是一颗独立的集成电路,而是由四个核心芯片组成:图形控制器(Graphics Controller)、时序发生器(Sequencer)、属性控制器(Attribute Controller)和 CRT 控制器(CRT Controller)。每个芯片都有自己独立的寄存器组。VGA 在此基础上增加了对更多图形模式的支持,但基本架构保持不变。
四平面内存组织:理解 VGA 帧缓冲区的关键
理解 VGA 内存访问复杂性的第一步是掌握其独特的四平面(Four Planes)内存组织结构。VGA 配备 256KB 显存,但由于其特殊的内存组织方式,在任何给定时刻,CPU 只能访问其中 64K 的唯一地址。这 64K 地址中的每一个位置后面都挂着四个独立的存储平面,每个平面存储一个比特位,组合起来形成 8 位像素数据。
在标准文本模式下,这种组织方式被巧妙利用:字符代码存储在偶数字节(平面 0 和 2),而属性字节存储在奇数字节(平面 1 和 3)。这种分离存储机制使得 CPU 可以高效地访问字符及其属性,但代价是需要复杂的寻址控制逻辑。
大多数 VGA 编程书籍和芯片数据手册对这一问题的描述往往是:“对于文本模式,将某位设为 1,将某位设为 0”。这种描述对于想要编程文本模式的用户来说足够有用,但完全没有解释这些位实际做了什么。由于 EGA/VGA 由多颗芯片组成,要实现某个功能(比如 CGA 文本模式兼容性或 CGA 图形兼容性),往往需要在两到三颗芯片中分别设置某些位。每个位确实有不同的功能,但用户被期望将它们作为一个整体来设置。更糟糕的是,没有任何机制阻止程序员单独翻转其中某个位,而这样做的后果往往令人意外。著名的 VGA Mode X 就是这种 “未文档化” 组合的典型应用案例。
Odd/Even 寻址的三位控制机制
在 VGA 模拟器实现中,最大的难点之一是如何正确处理 Odd/Even(奇偶)访问控制。这涉及到三个关键的寄存器位,它们都被标记为 “OE”(Odd/Even)但功能各异。时序发生器的 SR4 [2] 位、图形控制器的 GR5 [4] 位和 GR6 [1] 位,这三位看似做着相同的工作,实际上却控制着内存访问的不同方面。
理解这三位分离功能的关键在于:SR4 [2] 控制 CPU 写入操作,GR5 [4] 控制 CPU 读取操作,而 GR6 [1] 则控制寻址方式本身。换句话说,GR6 [1] 决定访问哪个内存地址,而 SR4 [2] 和 GR5 [4] 则决定在该地址上访问四个平面中的哪一个,但两者分别作用于写入和读取通道。
以标准文本模式为例:当 Odd/Even 寻址启用时,CPU 地址的 A0 位被固定,而不是直接来自 CPU。这样做的好处是,偶数地址自动访问平面 0 和 2(字符代码),奇数地址访问平面 1 和 3(属性)。这意味着 CPU 可以用简单的线性地址访问交错存储的字符和属性数据,极大地简化了文本模式的编程模型。
IBM 与 Compaq 文档质量的巨大差异
有趣的是,文档质量的差异直接影响了开发者对 VGA 内部机制的理解深度。IBM 官方的 VGA 技术参考手册对这些位的描述相当模糊。对于 SR4 [2],IBM 的描述是:“当 Odd/Even 字段设为 0 时,偶数系统地址访问映射 0 和 2,奇数系统地址访问映射 1 和 3;当设为 1 时,系统地址按顺序访问位图内的数据”。这个描述不能说错误,但几乎没有提供任何有用的实现细节。
相比之下,Compaq 的 EGA 技术参考指南提供了远为清晰的说明。对于 SR4 [2],Compaq 明确指出:“该位(odd/even 位)仅控制 CPU 写入访问。CPU 读取访问由图形控制器模式寄存器中的 odd/even 位控制”。这一句话就清楚地说明了 SR4 [2] 和 GR5 [4] 的分工。
Compaq 的文档还解释了 GR6 [1] 位的真正功能:当设为 0 时,CPU 地址的 A0 位被用作视频内存地址的 bit 0;当设为 1 时,A0 被一个更高位的 CPU 地址或页面选择位所替代。这种清晰的职责划分让实现者能够准确理解每个位的独立功能,而不仅仅是将它们作为一组来使用。
Matrox 文档补充的关键细节
即使 Compaq 的文档已经提供了很大帮助,仍有一些细节语焉不详。GR6 [1] 的描述中提到 A0 被 “更高位的 CPU 地址位或 MSR [5] 页面选择位” 替代,但具体是哪一位地址位,以及硬件如何决定使用哪一个,这些都是未解答的问题。
Matrox 的 MGA-1064 规格说明书填补了这一空白。它将 GR6 [1] 描述为 "Odd/Even chain enable":当设为 0 时,内存地址总线的 A0 信号用于系统内存寻址;当设为 1 时,允许 A0 被系统地址的 A16 信号(如果 memmapsl 为 00)或页面选择位(MSR [5])所替代。
这里的关键在于 GR6 [3:2] 位的 memmapsl 设置。当它们被设为 00 时,VGA 解码整个 A0000h-BFFFFh 地址范围,这是一种非常规设置。使用 GR6 [1] 后,用户可以通过 A0000h-AFFFFh 范围访问所有偶数地址的视频内存,通过 B0000h-BFFFFh 范围访问所有奇数地址。这正是 GR6 [1] 能够在 128K 地址空间内完整访问 64K 视频内存的机制。
当 GR6 [3:2] 设置为 00 以外的值时,MSR [5] 成为页面选择位,允许用户选择访问哪个 64K 页面。MSR [5] 的文档质量同样糟糕,不同来源对其极性描述相互矛盾:Matrox 声称置 1 选择 “高页面”,而 ATI 和 Cirrus Logic 的文档则声称 1 选择 “低页面”。
CRT 控制器的独立控制平面
值得注意的是,上述所有位仅控制主机 CPU 如何读写 VGA 内存。还有另一组完全独立的 CRT 控制器(CRTC)寄存器位,决定了显示扫描时如何从内存中读取数据。CRTC17 的多个位控制着 CRTC 计数器(16 位,A0-A15)如何转换为内存地址位(MA0-MA15)。
CRTC 的字访问模式(Word Access Mode)显然是 Odd/Even CPU 寻址的对立面。当字访问模式启用时,CRTC 地址的 A15 位成为内存地址的 MA0。这样,在 CRTC 地址范围 0000h-7FFFh 时显示偶数地址的内存数据,在 8000h-FFFFh 时显示奇数地址的数据。这种机制与 GR6 [1] 提供的功能完美对应。
为什么这让 VGA 模拟器开发者头疼
VGA 内存访问复杂性的核心问题在于:没有单一的 “启用文本模式” 位。CPU 内存写入、CPU 内存读取和 CPU 内存寻址各有独立的控制位。此外还有另一套控制 CRTC 操作的位。在正常使用中,这些位在 BIOS 模式设置期间被统一编程,但每个位都可以单独切换,产生大量 “未文档化” 的组合。
由于这些组合被认为没有用处,绝大多数 VGA 参考资料都选择忽略细节。然而,程序员确实会单独调整这些位,这意味着 VGA 硬件或模拟器需要正确实现每个位的精确行为才能达到近乎 100% 的兼容性。在文档不完整的情况下,这是一个艰巨的任务。
这种复杂性的后果在当代 VGA 模拟器中仍然可见。EMU86、DOSBox 等模拟器在某些边界情况下对 VGA 行为的模拟并不完美,部分原因在于原始文档的模糊性。幸运的是,像 OS/2 Museum 这样的资源正在通过深入研究填补这些空白,帮助后人更好地理解这段计算史上独特的硬件设计。
资料来源
- OS/2 Museum: Learn Something Old Every Day, Part XXI: VGA Memory Access Is Complicated
- Compaq Enhanced Color Graphics Board Technical Reference Guide (1986)
- Matrox MGA-1064 Specification
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。