【译制】红白机NTSC制式PPU——Ricoh 2C02技术参考(第一部分)
红白机NTSC制式PPU
——Ricoh 2C02技术参考

Brad Taylor(BTTDgroup@hotmail.com)
译者:某科学的小姚同志(bilibili@某科学的小姚同志)
第五版,2004年4月23日,译者修正了一些错误
译者技术及翻译水平有限,若有错误恳请大家指正!
向NES开发社区致谢,网址:http://nesdev.parodius.com
Neal Tew提供了关于屏幕卷动(Scrolling)的信息,向他表示特别感谢!
推荐文献:任天堂PPU的专利文件(U.S.#4,824,106)
注意:为了正确显示该文档,您的文本阅读器应该能够:
1、支持传统VGA文本模式显示,具有含256个字符的字符集,能显示线条画字符;
2、支持自动换行,将字体切换为终端风格的Windows记事本程序能轻松满足这两项要求

在下文中,将会讨论
2C02内部集成部件列表
2C02引脚命名法和信号描述
2C02的编程模型
视频信号的产生
PPU的基本时序
PPU的其他杂项信息
PPU的显存访问周期
帧渲染细节
扫描线渲染细节
画面内精灵(Sprite/Object)的判定
背景渲染流水线的细节
精灵样式(Pattern)的取出和渲染细节
关于那些“多一个周期”的帧
MMC3的扫描线计数器
PPU像素优先级的一些”怪事“(BUGs)
PPU屏幕卷动和寻址简述

内部集成部件列表
- 控制寄存器和杂项寄存器
- 像素和扫描线计数器
- 彩色突发信号(Colorburst)发生器
- 显存(VRAM)地址锁存和计数器
- VRAM地址缓冲器,也称图块(Tile)索引字节
- VRAM读写缓冲器
- 精灵属性内存(OAM,Object Attribute Memory)
- OAM指针寄存器/计数器
- OAM临时内存和扫描线比较器
- 精灵垂直和水平翻转逻辑
- 精灵像素缓冲器
- 背景像素缓冲器
- 多路复用器
- 系统调色板ROM和用户调色板RAM
- 屏幕卷动寄存器和计数器
- 电平译码器/相位(Phase)选择器/数模转换器(DAC)
- 地址/数据引脚复用字节指针触发器

2C02引脚命名法和信号描述
___ ___
| * \/ |
R/W >01] [40< VCC
D0 [02] [39> ALE
D1 [03] [38] AD0
D2 [04] [37] AD1
D3 [05] [36] AD2
D4 [06] [35] AD3
D5 [07] [34] AD4
D6 [08] [33] AD5
D7 [09] [32] AD6
A2 >10] 2C02 [31] AD7
A1 >11] [30> A8
A0 >12] [29> A9
/CS >13] [28> A10
EXT0 [14] [27> A11
EXT1 [15] [26> A12
EXT2 [16] [25> A13
EXT3 [17] [24> /R
CLK >18] [23> /W
/VBL <19] [22< /SYNC
VEE >20] [21> VOUT
|_________|
R/W, D0-D7, A2-A0, /CS:
这些是PPU的控制总线信号,通过这些信号来编程2C02的内部寄存器。R/W控制数据传输方向(低电平代表向PPU写数据)。通过A0-A2来选择要读写的PPU内部寄存器。当/CS(片选信号)为低电平时,D0-D7被用作与选中的寄存器收发数据(若/CS为高电平,则D0-D7为高阻态)。文档的下一章节将详细介绍PPU内部寄存器的操作。
EXT0-EXT3:
这些引脚可用作像素输入(用作将外部产生的图像与2C02产生的图像叠合),也可用用作输出(驱动另一片PPU)。通过对2C02编程,可配置这些引脚的功能(输入/输出)。NES/FC主板将这些引脚接地(全黑像素),因此它们通常被编程为输入模式。
CLK:此引脚为2C02的21.48MHz时钟信号输入。
/VBL:
此引脚在PPU进入场消隐期(VBLANK)时变化为低电平,并在低电平维持长达20条扫描线的时间。它常常与2A03 CPU的/NMI(不可屏蔽中断)引脚相连,用于在每帧前产生一次不可屏蔽中断。我们可以通过软件快速清零/置位PPU寄存器中/VBL位来响应这个中断,故此中断信号的持续时间往往不足一条扫描线。此引脚内部为开集(Open-collector)连接方式。
VEE, VCC:对应为接地和5V直流电源输入。
VOUT:
此引脚为2C02无缓冲的复合视频信号输出端。它通常与一个两级的共集电极三极管放大器连接,从而将视频信号放大到1V峰-峰值,驱动75欧姆的负载。
/SYNC:
当此引脚为低电平时,PPU内部的颜色突发控制、扫描线和像素计数器/触发器都会被复位到一个确定的状态。当两片2C02通过EXT引脚以主-从方式相连时,双方通过此引脚进行同步,即主PPU的/VBL引脚产生场消隐信号,该信号输入到从PPU到/SYNC端口。在Famicom主机中,该引脚被接于逻辑高电平上,
而在NES主机中,该引脚于2A03 CPU到复位信号相连,这导致当复位按键被按住时无图像输出。
/R, /W, ALE, AD0-AD7, A8-A13:
这些引脚用来控制PPU相关的数据总线(与显存相连)。PPU将地址第0-7位放在AD总线上时,ALE输出高电平(通常用一片74LS373锁存器来锁存地址总线低八位的数据)。/R或/W引脚低电平有效,指示PPU的AD总线上连接的存储器将14位地址信号(A0-A7由外部锁存器提供,A8-A13由PPU直接提供)译码,并根据传输方向的指示提供数据(/R信号有效表示将数据送入2C02,/W信号有效则相反)。/R、/W和ALE信号在同一时间至多有一个有效。

2C02的编程模型
本章节将讨论2C02的端口(寄存器)和可编程显存的组织结构。文档中这些端口使用$200加上末尾的一位数来表示(例如$2002)。此处未说明的内容将在下文解释。

可写的2C02端口
端口编号 位 描述
-------- -- ----
0 0 水平命名表选择
1 垂直命名表选择
(0-1两位共同选择了2*2的4个命名表中的一个作为当前命名表)
2 7号端口访问时的PPU地址自增选择(0:自增1-横向,1:自增32-纵向)
3 精灵样式表选择(第5位为0时该选择才有效)
4 背景样式表选择
5 精灵高度选择(0:8线精灵,1:16线精灵)
6 EXT引脚数据方向(0:输入,1:输出)
7 /VBL信号无效控制(为0)
1 0 禁用复合视频信号的彩色突发(1为禁用),会产生灰度图像
1 最左侧8列背景像素裁剪(0为有效)
2 最左侧8列精灵像素裁剪(0为有效)
3 打开背景显示(1为有效)
4 打开精灵显示(1为有效)
5 红色通道亮度强调/补偿
6 绿色通道亮度强调/补偿
7 蓝色通道亮度强调/补偿
3 - OAM(在PPU内部)索引指针(64个表项,每个32位,访问粒度为字节)
4 - OAM数据写入端口(写入地址通过3号端口设置,写入后地址自增)
5 - 屏幕卷动偏移设置端口
6 - VRAM读/写地址端口(通过7号端口访问VRAM)
7 - VRAM数据写入端口

可读的2C02寄存器
端口编号 位 描述
------- -- -----
2 5 指示读取前最后一帧中单一扫描线上检测到了多于8个精灵
6 指示读取前最后一帧中最高优先级的精灵(0号精灵)与背景非0像素发生碰撞(重合)
7 场消隐标志
4 - OAM数据读出端口(读出地址通过3号端口设置,读出后地址自增)
7 - VRAM数据读出端口

精灵属性结构体(OAM表项,一个精灵占用4个字节)
字节 位 描述
--- -- -----
0 - 精灵顶部的坐标(Y坐标),用(扫描线坐标-1)表示
1 - 精灵的样式表(VRAM中)索引号,当使用16线高度的精灵时,该字节的第0位用于选择样式表
2 0 精灵调色板项选择(第2位)
1 精灵调色板项选择(第3位)
(第0-1位相当于该精灵的属性表;调色板项第0、1位存放在精灵的样式表中)
5 精灵相对于背景的优先级(0:优先级高于背景,1:优先级低于背景)
6 水平翻转精灵(将从样式表取出每一字节数据的位顺序反转)
7 垂直翻转精灵(访问样式表时,将8线矮精灵访问地址的第3位或16线高精灵访问地址的第4位取反)
3 - 精灵左侧边的坐标(X坐标),用扫描线坐标表示

视频信号的产生
一个21.48MHz的时钟信号被输入2C02作为其主时钟,同时该时钟也被2A03 CPU共享。
在PPU的内部,21.48MHz时钟被用来驱动一个三位的约翰逊(纽环)计数器(6种有效状态:000->100->110->111->011->001)。计数器每一位都由一个主-从D触发器实现。这3位对应主-从触发器的主、从两部分的输出合起来正好可以编码产生6*2=12个相位(Phase)彼此不同的时钟信号,其中每个信号的周期都是3.58MHz(=21.48MHz/6,为NTSC彩色突发周期,彩色突发信号为显示器给出了用来区分用来代表不同色相的相位的基准相位)。这12个相位不同的信号是PPU产生彩色复合视频输出信号的基础。
当用户编程改变像素对应用户调色板所索引的系统调色板的低四位时,相位信号1-12之一就会被路由到PPU的视频输出引脚上(这决定了视频信息中的色相)。系统调色板低四位色相中的0和13则被简单地硬连接到高电平或低电平以产生灰阶(消色)像素信号。
系统调色板颜色号的第4和第5位选择线性直流电压偏置信号1-4之一,该偏置作用于像素的色相信号上,决定了视频信息中的亮度。
色相选择14和15在任何亮度选择下都产生黑色像素。
亮度选择0和色相选择13会产生一种“比黑色还要黑”的像素颜色。这种超级黑的像素的输出电平接近场/行同步脉冲,因此,使用这种黑色的游戏(游戏精灵Game Genie就是最好的例子)会导致某些显示器产生画面撕裂或失真。此时,这种扫描线内的伪同步脉冲被显示器当作正常的同步脉冲,将行扫描时序打断。这种现象不会损坏显示器,但我们仍需要避免使用到这种“超级黑色”,防止产生图像失真。
系统调色板低四位选出的色相信号的交流幅值始终保持恒定,不随着系统调色板第4位和第5位的改变而改变。因此,我们没有办法调节特定颜色的饱和度。

PPU的基本时序
除了三位约翰逊计数器,21.48MHz的并不被其他PPU硬件组成部分直接使用。然而,此信号被四分频,得到5.37MHz时钟,作为PPU时序的最小单位。如无特殊说明,本文档以下所有PPU时钟周期都是以该时钟基准为单位的。
- 像素渲染的速度与PPU基础时钟一致,即1像素=1时钟周期
- 341个PPU时钟周期(折合341/3个CPU时钟周期)为一个典型的扫描线周期
- 1帧包含262条扫描线,也就是说一帧有341*262个PPU时钟周期(除以3即为对应CPU的时钟周期)

PPU的显存访问周期
所有PPU显存访问周期都是两个时钟周期长的,且可以连续发生(通常在渲染期间完成)。显存访问可以拆分为以下步骤:
在访存周期的起始,目标地址的高位被更新到PPU地址线A8-A13上。这些地址信号将被保持直至下一次访存周期到来。
为了减少PPU的引脚数目,PPU低8位地址线的和数据总线复用。在访存的第一个时钟周期,地址A0-A7被放在PPU的数据总线上,同时ALE(地址锁存使能)信号在后半个周期有效。这一操作使得低8位地址进入受ALE信号控制的外部8位锁存器(一般使用74LS373锁存器)。
在第二个时钟周期,/RD或/WR信号有效,并在整个周期中保持有效。此时,相应数据被VRAM放在数据总线上。

PPU的其他杂项信息
- 通过读写地址在 $3Fxx范围内的端口,可以访问PPU内部含有25个元素的用户调色板RAM。该地址的第四位用来指定访问背景调色板(0)还是精灵调色板(1)。地址的第3-2位用来索引调色板表项(0-3),而第1-0位用来索引调色板表项的元素(0或1)。当地址的第0-3位均为0时,会访问相同且唯一(共享)的一个透明色(背景色)调色板元素。
- 从端口 $2002 读取数据会清除第七位的场消隐标志,并复位PPU内部 $2005/6 端口的触发器(寄存器/缓冲器)。向 $2002 写数据则没有这样的效果。
- 2C02的引脚 /VBL的输出信号是 $2002 寄存器的第七位和 $2000寄存器 的第七位做与非逻辑运算产生的。
- $2002 寄存器的第五位和 $2002 寄存器的第六位被PPU内部逻辑置位后会相对于场消隐信号于新一帧的开始处保持20个扫描线的长度。
- 用户调色板RAM在背景渲染期间会被PPU内部所访问(期间用户调色板的地址和数据不可能出现在VRAM总线上)。哪怕是此时程序员通过编程控制CPU访问 $2006/7 端口以试图访问用户调色板RAM,虽然被访问的调色板地址确实被放在了VRAM地址总线上,但VRAM的读写信号 /RD 和 /WR 也不会有效。这个机制原本设计的用意是防止像素渲染期间读写VRAM镜像地址区修改命名表,导致渲染出错(PPU内部的命名表RAM地址译码器是一个连接在VRAM A13地址线上的非门,这导致读写VRAM $2000-$3FFF地址区均被判定为读写命名表)。
- PPU在设计上无法直接使用CPU通过 $2007 端口给出的VRAM地址去访问VRAM,而是经过一个内部缓冲器实现的,缓冲器本质上是一个只有一级的流水线。当有VRAM读取请求时,缓冲器的内容会被返回给CPU。此后,PPU会尽快于PPU时序读VRAM的周期里从VRAM将请求的数据取回并放入读缓冲器中。通过 $2007 端口向VRAM写入数据也是经过流水线缓冲的,但目前还不确定数据流经的缓冲区和读缓冲器是否为同一个缓冲器(猜测可以通过向 $2007 端口写入数据并迅速读回数据,检查两个数据是否一致来简单地判断)。

帧渲染细节
以下描述说明了PPU在一帧共262条有效扫描线中的行为。对应每一条有效(如存在图像渲染行为)的扫描线,PPU分别有如下描述的动作:
第0-19条扫描线:自场消隐标志VINT被设置(同时产生NMI不可屏蔽中断信号)而开始,20条扫描线组成了习惯上被成为场消隐期的时期。在此期间,PPU不访问VRAM(命名表、样式表等)。
第20条扫描线:自VINT场消隐标志被置位20条扫描线的时间过后(场消隐结束),PPU开始渲染扫描线。此后第一条扫描线(第20条)是“哑扫描线”,尽管在此期间会和那些显示图像的扫描线一样从VRAM按顺序取回数据,但屏幕上并没有可见像素被渲染出来,因而此时从VRAM取回的背景数据也是无关紧要的。在该扫描线的第256个时钟周期处水平和垂直卷动寄存器的内容被装入对应计数器(据猜测)。除此之外,这条扫描线的行为与其它扫描线有很大区别,其主要原因在于此扫描线的存在被用作启动精灵渲染流水线,即它会花费256个时钟周期的时间来判断第一条可见扫描线(第21条)上是否存在及存在哪些精灵。
第21-260条扫描线:在渲染完一条“哑扫描线”后,PPU开始渲染实际可见的扫描线,将实际的显示数据输出到显示屏上。这一阶段会渲染240条扫描线(NES/FC的分辨率为256*240)。
第261条扫描线:在最后一条可见扫描线渲染完成后,PPU会等待一条扫描线的时间且没有动作(起“补偿”作用,使编程者能得到“完美的”场消隐中断周期)。此扫描线结束时,场消隐标志VINT被置位,开始下一场(帧)的第一条扫描线的计时。

(未完待续)