VGA 内存访问复杂
摘要
本文探讨了 VGA 内存访问的复杂性,以及由于文档不完善而在模拟旧硬件方面所面临的挑战。
暂无内容
查看缓存全文
缓存时间: 2026/05/12 03:56
# 每天学点老知识,第二十一篇:VGA 内存访问很复杂
来源:http://www.os2museum.com/wp/learn-something-old-every-day-part-xxi-vga-memory-access-is-complicated/
上周我再次与 VGA 模拟发生了冲突,和上次一样(http://www.os2museum.com/wp/learn-something-old-every-day-part-x-the-vga-attribute-controller-is-weird/),原因很可能是 VGA 文档普遍存在严重不足。
总体而言,VGA 并不是一种特别复杂的硬件。它没有微码,没有 CPU/微控制器,执行的功能相对简单。然而,VGA 由多个逻辑上独立的组件组成,它们协同工作,这带来了一层或许被低估的复杂性。
在实现 VGA 硬件或模拟时,最大的困难莫过于缺乏*详细*且准确的文档。这个问题最早可以追溯到 IBM EGA,因为 VGA 只是 EGA 的一个稍微扩展的超集。
IBM EGA(Enhanced Graphics Adapter,增强图形适配器)旨在支持高分辨率(按照 1984 年的标准)图形,最多 16 种颜色,可编程字体,同时与 MDA/CGA 文本模式以及 CGA 图形模式保持高度的兼容性。
然而,EGA 与早期的 IBM 适配器并不寄存器兼容(其内部架构也有很大不同)。使用直接设置 CGA 图形模式的代码(例如)在 EGA 上无法工作。有效的方法是通过 BIOS 建立模式,然后像访问 CGA 一样访问视频内存。
与此同时,EGA 的年代足够久远,它并不是单个集成芯片。它由四个核心芯片构成:图形控制器(Graphics Controller)、排序器(Sequencer)、属性控制器(Attribute Controller)和 CRT 控制器(CRT Controller)。每个芯片都有自己的一组寄存器。
EGA/VGA 文档的难点在于,绝大部分文档(从 IBM 自己的技术参考文档开始)都是写给*使用* VGA 的人看的,而不是写给*实现* VGA 功能的人看的。而且这些文档假设用户主要关心的是 BIOS 支持的标准模式。
大多数 VGA 参考手册会这样说:“对于文本模式,将位 X 设为 1,位 Y 设为 0”。如果你想编程文本模式,这很有用,但它完全没有解释这些位实际上*做什么*。因为 EGA 由多个芯片组成,常见的情况是,必须将两到三个芯片中的位以特定方式设置,才能实现例如 CGA 文本模式兼容或 CGA 图形兼容。显然,每个位都有不同的功能,但用户“应该”将它们一起设置。同时,没有什么能阻止程序员只翻转一组三位中的一两个位,然后会发生什么?一些“未记录”的组合结果证明非常有用,比如著名的 VGAMode X(https://en.wikipedia.org/wiki/Mode_X)。
我试图解决的问题与 VGA(或者说其实是 EGA)排序器和图形控制器中的奇偶控制寄存器有关。奇偶访问用于文本模式,其名称引用了这样一个事实:字符代码存储在偶数字节中,属性存储在奇数字节中,尽管实际上它们存储在 EGA/VGA 四平面内存组织的不同平面中。
### IBM...
作为对比,让我们看看 IBM VGA 技术参考手册(TRM)(https://bitsavers.org/pdf/ibm/pc/ps2/42G2193_PS2_Hardware_Interface_Technical_Reference_Video_Subsystems_Sep92.pdf#page=78&zoom=page-fit,-133,613) 对这些位的说法:
- SR4\[2\],位 OE:当奇偶字段(位 2)设为 0 时,偶数系统地址访问映射 0 和 2,而奇数系统地址访问映射 1 和 3。当设为 1 时,系统地址按顺序访问位图内的数据,并根据映射掩码寄存器(十六进制 02)中的值访问映射。
- GR5\[4\],位 OE:当设为 1 时,奇偶字段(位 4)选择 IBM Color/Graphics Monitor Adapter 使用的奇偶寻址模式。通常,这里的值跟随排序器中内存模式寄存器位 2 的值。
- GR6\[1\],位 OE:当设为 1 时,奇偶字段(位 1)指示系统地址位 A0 被更高阶位替换。然后,当 A0 为 1 时选择奇映射,当 A0 为 0 时选择偶映射。
文档中将这三个位都称为“OE”。注意 GR5\[4\] 的描述并没有真正说明任何内容,除了 GR5\[4\] 的值应该“跟随” SR4\[2\] 的值。GR6\[1\] 的文档说明了该位设为 1 时发生的情况,但没有明确说明将该位设为 0 时发生的情况。
从这些描述来看,几乎不可能推断出这些位各自的作用,只知道它们都需要以特定方式设置才能模拟 CGA 文本或图形模式。
大多数 VGA 编程书籍和芯片数据手册与 IBM 的文档类似。描述并不*错误*,但相当不充分。幸运的是,并非所有参考资料都是这样。
### ... 对比 Compaq
一个意想不到的有益信息来源竟是 Compaq 的 EGA TRG(技术参考指南)(https://archive.org/details/trg-enhanced-color-graphics-board-1986-12)。
以下是 Compaq 如何记录相同的位(在 EGA 中,但在 VGA 上工作方式相同):
- SR4\[2\]:0 = 偶数 CPU 地址访问平面 0 和 2。奇数 CPU 地址访问平面 1 和 3。1 = CPU 地址按顺序访问平面中的数据。位 <2>(奇偶位)*仅*控制 CPU 写访问。CPU 读访问由图形控制器模式寄存器中的奇偶位控制。
- GR5\[4\]:奇偶位 0 = CPU 从平面按顺序读取数据 1 = 偶数 CPU 地址访问平面 0 和 2。奇数 CPU 地址访问平面 1 和 3。
- GR6\[1\]:0 = CPU 地址 A0 用作视频内存地址位 <0> 1 = CPU 地址 A0 被更高阶 CPU 位或控制和状态杂项输出寄存器中的页面选择位(位 <5>)替换。
此外,寄存器 GR4(图形控制器读平面选择)的文档包含一个方便的表格,准确显示了 GR4 寄存器和 GR5\[4\] 位如何交互以确定读平面选择。
Compaq 的文档表面上与 IBM 的相似,但解释了多得多的内容。它明确指出 SR4\[2\] 控制 CPU 写入,而 GR5\[4\] 控制读取。这些位具有明显且独立的功能,即使它们*通常*是配合编程的。
Compaq 还解释说,虽然 SR4\[2\] 和 GR5\[4\] 分别控制写入和读取的平面选择,但位 GR6\[1\] 控制寻址。换句话说,GR6\[1\] 确定访问哪个*内存地址*,而 SR4\[2\] 和 GR5\[4\] 控制在该内存地址下访问四个*平面*中的哪一个,且写入和读取是分开控制的。
正如人们可能怀疑的那样,这三个独立的位确实具有各自独立的功能——这毫不奇怪,因为如果它们没有,IBM 就不需要这三个位了。顺便说一句,这些位只控制主机 CPU 如何读取和写入 VGA 内存。CRT 控制器(CRTC)中还有另一组完全独立的位,决定 VGA 内存中的数据如何在屏幕上显示。
老实说,感谢 Compaq 花时间解释这些位真正*做什么*,而不是仅仅含糊地挥手说明它们*打算如何使用*。
### 疑问依然存在
即使 Compaq 的文档也不完整。GR6\[1\] 的文档指出,地址位 A0(来自 CPU)“被更高阶 CPU 位或页面选择位替换”。但它没有说明是*哪个*更高阶的 CPU \[地址,推测是地址位\] 位,或者硬件*如何*决定是使用 CPU 地址位还是 MSR\[5\] 位。
我在 Matrox MGA-1064 规格书中找到了答案(就 VGA 寄存器而言,它与 MGA-2164、Matrox G100、G200 和类似文档非常相似)。Matrox 对 GR6\[1\] 位的描述如下:
- 奇偶链使能。VGA。0:内存地址总线的 A0 信号在系统内存寻址期间使用。1:允许 A0 被系统地址的 A16 信号替换(如果*memmapsl* 为 '00'),或者被*hpgoddev*(MISC<5>,奇偶页面选择)字段替换。
嗯,这很有趣。*memmapsl* 位是 GR6\[3:2\],将它们设为 00 意味着 VGA 解码整个 A0000h-BFFFFh 范围。这个设置*非常*不寻常,在实践中不使用。但它实际上是有道理的。
VGA(和 EGA)拥有(或者在 EGA 的情况下可以升级到)256K 内存。然而,由于其组织为四个独立的平面,只有 64K 唯一地址,每个地址后面有四个平面(每个平面上的每个地址有八位)。当启用奇偶寻址时,只访问 VGA 内存中的每隔一个地址,因为 A0 位是固定的(即,它不来自 CPU)。当 GR6\[3:2\] 位被编程为 00 时,可以使用 128K 范围来访问 64K 唯一地址……乍一看这没有意义——除非将 GR6\[1\] 设为 1 允许用户通过 A0000h-AFFFFh 访问 64K 范围内的所有偶数地址,并通过 B0000h-BFFFFh 范围访问所有奇数地址。而这正是通过将来自 CPU 的 A0 位(通常控制文本模式中的奇偶平面选择)替换为 A16 来实现的。
当 GR6\[3:2\] 位被设为非 00 的值(解码 64K 或 32K 的地址空间)时,MSR\[5\] 成为“页面选择”位,允许用户选择访问哪个 64K。
MSR\[5\] 位的记录极差,不同来源对其极性说法不一。有些(例如 Matrox)声称将 MSR\[5\] 位设为 1 选择“高页面”,有些(例如 ATI 264VT, Cirrus Logic Alpine)声称 1 选择低页面。MSR\[5\] 位在硬件复位后默认为 0 这一事实进一步混淆了问题,但几乎所有 EGA/VGA BIOS 都将其设为 1。
不用说,还有完全独立的 CRTC 控制位,它们决定 VGA 内存中的数据如何被显示。位 CRTC14\[6\], CRTC17\[0\], CRTC17\[1\], CRTC17\[5\] 和 CRTC17\[6\] 控制 CRTC 在刷新显示时如何寻址内存。Matrox 对 CRTC17 的文档包括三个方便的表格,显示了 CRTC 位如何以相当有趣的方式将 CRTC 计数器(16 位,A0-A15)转换为内存地址位(MA0-MA15)。
CRTC 的字访问模式显然被设计为奇偶 CPU 寻址的对应物——当然,没有任何东西*强制*程序员将两者一起使用。在字访问模式下,CRTC 地址位 A15(假设通过设置 CR17\[5\] 为零没有强制地址回绕)变为内存地址位 MA0。此机制让 CRTC 在 CRTC 地址处于 0000h-7FFFh 范围时显示 VGA 内存中的偶数地址,在 8000h-FFFFh 范围时显示奇数地址。这完美映射到由 GR6\[1\] 提供的功能。
我不知道有任何软件实际使用 MSR\[5\] 或 128K VGA 地址范围来增加 EGA/VGA 文本模式的内存容量。不过,这些位是存在的,几乎可以肯定*某些*有进取心的程序员使用过它们。
所有这些位是否在常见的 VGA 实现中都能正常工作,这是另一个问题。不完整的文档可能阻止了程序员发挥创意,这反过来掩盖了由不完整或不准确的 VGA 实现引起的问题。
### 为什么它很复杂
以上段落说明了为什么 VGA(和 EGA)很复杂。没有一个单一的“启用文本模式”位。有一个用于 CPU 内存写入的控制,另一个用于 CPU 内存读取,另一个用于 CPU 内存寻址。然后还有一整套控制 CRTC 操作的位。
在典型情况下,这些位在 BIOS 模式设置期间作为一个单元进行编程。然而,所有这些位*可以*单独切换,这产生了大量“未记录”的组合。因为这些组合被认为没有用,绝大多数 VGA 参考资料只是略过细节。
关键在于,程序员可以并且确实会调整各个位,这意味着为了实现 100%(或至少非常高)的兼容性,VGA 硬件或模拟需要正确实现每一位的行为。但没有充分的文档,这可不是一项简单的任务。
相似文章
SNES: 精灵与背景渲染
本文解释了SNES的PPU如何在紧张的VRAM带宽限制下渲染精灵和背景,描述了不同视频模式中的硬件权衡。
模拟器调试:Area 5150 的 Lake Effect
本文详细介绍了在MartyPC模拟器上调试Area5150演示中“Lake”效应的过程,解释了需要特定标题hack的原因,以及通过总线嗅探和动态时钟实现周期精确CGA模拟的后续修复方法。
LLM的GPU内存计算 (2026版)
一份实用指南,解释了如何根据参数量和量化级别计算LLM的VRAM需求,以及KV缓存、激活值和批处理带来的额外开销。
80386 早期启动内存访问
本文解释了 Intel 80386 中的早期启动内存访问技术,该技术通过将地址生成与前一条指令的最后一个周期重叠来隐藏内存延迟。文章描述了该技术在 z386 FPGA 核心中的实现,达到了 ao486 级别的性能,并在 Doom FPS 上提升了 39%。
IBM MCGA 门阵列逆向工程
该项目对 IBM 用于 PS/2 25 型和 30 型中的 MCGA 门阵列进行逆向工程,揭示了详细的电路图和新功能,例如 genlock 能力和未公开的寄存器。