前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >NES基本原理(一)总述

NES基本原理(一)总述

原创
作者头像
rand_cs
发布于 2023-12-16 05:36:37
发布于 2023-12-16 05:36:37
7740
举报
文章被收录于专栏:计算机学习计算机学习

总述

本来想直接开始写 CPU 的,想了想,还是先来写个总述,好有个大致概念。

省去了很多东西,但总体大概就这么个样子。手柄是输入设备,电视为输出设备,CPU 为处理器,PPU 为图形处理器,卡带可以看作是存储的一部分

任天堂给出的与 CPU 相关的总线图如下所示:

可以看出地址总线为 16 位,但是数据总线只有 8 位

按照上图所示,其中 CPU 的地址空间分为三部分,一部分为 ROM,它位于卡带里面,RAM 为 CPU 自个儿的内存,I/O 为内存映射的一些寄存器,CPU 通过这些寄存器来与 PPU,手柄等通讯

任天堂给出的那张图对于 CPU PPU 地址空间的描述不是很清楚,自个儿画了张图,如上所示,先简要解释出现的名词:

CHR:Character Memory,存放游戏使用的图案

PGR:Program Memory,存放游戏代码

Nametable:VRAM,这部分是 PPU 自个儿的内存,存放图案的索引还有一定的颜色信息。

Pallete:调色板,渲染时使用的颜色(存放的是颜色的索引)

CPU RAM:CPU 自个儿的内存

CPU 的地址空间主要由三部分组成,从低到高依次为:CPU RAM -> 内存映射寄存器 -> PRG

PPU 的地址空间也由三部分组成,从低到高依次为:Nametable->CHR->Pallete

内存映射的寄存器是 CPU 用来和其他子系统通讯使用,比如说与 PPU 通讯,获取 PPU 地址空间里面的数据,与输入设备手柄通讯,与内部的 APU 通讯等等

另外要注意,虽然 PRG 和 CHR 两者在卡带里面,但是映射到了不同的地址空间

下面简要的说说这几个部分:

先从卡带说起,卡带里面有主要有两块存储芯片,第一个是 PRG,Program Memory,是只读的 ROM,看名字大概就能够猜到,这里面存放的是游戏代码,这个游戏代码交由 CPU 来运行

第二个是 CHR,Character Memory,ROM 和 RAM 都有可能,不同的卡带里面不同。这里面存放的是游戏所用到的图案。比如说咱们熟悉的超级马里奥的 CHR:

这是我用 FCEUX 打开超级马里奥之后使用其 PPU Viewer 工具截取的图,有兴趣的可以下载这个模拟器试试。

可以看出超级马里奥的 CHR 里面主要有两个 PatternTable (图案表),这两个 PatternTable 没有明确的名称,但是在超级马里奥里面可以看出,左侧的 PatternTable 主要是精灵使用(精灵就是角色血条分数等等),右侧主要是背景使用。

每个 PatternTable 由 256 个“小块” 组成,这么一个 “小块” 叫做 tile (中文翻译过来叫瓦片?感觉不太好,后面我直接使用英文 tile),一个 tile 就是 $8 \times 8$ 的像素集合

要表示一个像素图案,1 个像素可以用 1bit 来表示,比如说 0 表示透明,1 表示不透明,那么 $8 \times 8=64bit$ 就可以表示一个图案,但 tile 用 2bit 来表示一个 像素,举个例子:

这大致能看出来是是什么吧,这个 tile 是长大的马里奥头部的右上角,所以 tile 本质上是什么,一个 tile 就是 64 个 2bit 信息,但是要注意一个像素的 2bit 是分开存储的,如上图上侧所示。

为什么要用 2bit 来表示一个像素,这是因为这 2bit 可以在 4 种颜色当中索引(其实只有 3 种,因为有个透明"色"),所以可以知道一个 tile 种绝对不会超过 4 种颜色。还记得我前文说的“抠门”吗?这算是 “抠门” 之一吧。

因此对于 PatternTable,虽然叫做 PatternTable,但它存储的不只是图案信息,还有颜色信息

大家都知道,所谓的视频、连续的画面都是一帧一帧的图片组成,而 NES 里面,显示到电视的每一帧图片都是由一个个 tile 组成

那一帧的图片由多少个 tile 组成呢?也就是说图片的像素多少?每一帧图片是 $256 \times 240$ 像素,也就是由 $32 \times 30 = 960$ 个 tile 组成

960 个 tile 一定是按照某种次序排列的,才能形成正确的图片,那么在哪记录这个次序呢?这就是 PPU 自个儿的 RAM,也就是显存 VRAM,又叫做 Nametable。

一屏或者说一帧图片有 960 个 tile,但记录在 Nametable 中的信息并不是 tile 本身,而是记录 tile 的索引。举个例子:

如上图所示,M 使用索引为 \$16 的 tile,金币使用的是索引为 \$2E 的 tile,草垛和云的一角都使用的是索引为 \$36 的图案,\$ 表示十六进制,在 NES CPU 的汇编里面使用 \$ 表示十六进制而不是 0x。

某个时刻背景只会从某个 PatternTable 中选取 tile,从上面的所举的马里奥例子中来看,此时背景选用的 PatternTable 为右侧的PatternTable。

另外还可以观察到一个事实:云朵和草垛其实是一个东西,只是颜色不一样而已由此也可以想到:看起来画面丰富多彩,其实很多都是一样的东西,一帧画面再丰富多彩,都不可能超过 $256 \times 2=512$ 个 tile 样式。这算是“抠门”之二吧

所以现在知道了 PPU 的 Nametable 主要就是拿来存放这么一个个的 tile 索引,这里我们可以简单的计算一下:

一个 PatternTable 256 个 tile,所以索引需要 8 bits,一屏有 960 个 tiles 组成,即需要 960B 存放一屏的索引,Nametable 有两个,每个 1KB = 1024B,所以 2 个 Nametable 可以放下 2 屏的 tile 索引

物理上有 2 屏,但是逻辑上抽象出来 4 屏,其中 2 屏是镜像,这是后话了,这里需要知道的是,有了这更大的空间存放索引,使得滚屏更容易实现(emm也不容易),举个横向滚屏的例子:如下图所示:

上图的上侧就是两屏的索引,只是以图案代替了,那个方框就是屏幕,方框里面的东西就是会渲染到屏幕上的,如下侧所示。

所以现在我们知道了 Nametable 里面就是存放了两屏的 tile 索引,PPU 将会根据这两屏的索引渲染背景。

那这个 Nametable 是被谁填充的呢?这个就是由游戏代码控制的了,什么时候填充,填充什么,都是由代码来进行逻辑控制。

回到前面那简单的计算,一个 Nametable 1024B,但是一屏的 tile 索引只用了 960B,还有 64B 呢?丢了不要了?这不可能,那个年代,寸土寸金,没有浪费这一说。

这 64B 叫做 Atrribute Table,其实就是背景的颜色属性。什么?64B 可以记录一屏的颜色属性?当然不可能,但也差不多了。

说到这里就顺便说说颜色,颜色的表示有多种方式,比如最熟悉的 RGB,一个三元组(R, G, B)就能表示一种颜色,每个元素的取指范围为0, 255。

在 NES 里面,理论上能使用的颜色有 64 种,还记得 前面地址空间结构图吗,其中 PPU 里面有一块空间专门来存放颜色信息,但存放的并不是颜色本身,而是 NES 调色板里颜色的索引

PPU Pallete 里有 8 个条目,每个条目有 4 种颜色的索引。背景只能使用前 4 个,而精灵只能使用后 4 个,感觉挺抠是吧,这还没完,背景使用的 Pallete 都应有相同的一个背景色,所以背景其实只能使用 $3 \times 4 + 1 = 13$ 种颜色,而精灵需要有透明色,所以精灵实际只有 $3 \times4 = 12$ 种颜色可用

回到 Attribute,Attribute 就是用来选取哪个 Pallete 条目,但 64B 的 Attribute 要为 $32 \times 30$ 个 tile,$256 \times 240$ 的像素选取 Pallete,那必然会使得一些 tile 使用相同的 Pallete,这也是为什么这些游戏的颜色如此单调的原因,这也算是 “抠门” 之三。具体颜色如何选取抉择,还是有些复杂,三言两语说不清,留待后面详述。

前面 PatternTable,这里的 AttributeTable,Pallete,都在说颜色,颜色到底怎么回事?来捋一捋:AtrributeTable 用来选取 Pallete 条目,PatternTable 用来选取 Pallete 中的颜色索引,这个索引又指向实际的颜色

前面都在说背景,这里再来说说精灵,不知大家有没有注意到,上述的一些图片少了些什么不?少了角色等精灵,精灵与背景是分开单独控制的。

PPU 的内部有专门的 RAM(地址空间中没显示,不能直接访问,需要通过特定的端口来访问) 来存放精灵的信息,这部分空间叫做 OAM(Object Attribute Memory),也叫做 Sprite RAM。OAM 中能存放 64 个精灵条目,但是每次最多只能渲染 8 个精灵

每个精灵条目控制着精灵的一些属性,比如说这个精灵使用的哪个 tile,也就是 tile 索引,还有精灵的位置,即 X,Y 坐标,另外就是该精灵的使用的 Pallete 条目,是否翻转等信息

举个例子简单说明,比如说长大的马里奥:

明显的,这长大的马里奥由 8 个 tile 组成,具体是 PatternTable 里面哪个 tile 我就不去标注了。

仔细看这个马里奥的话,还可以发现他的下半部分是对称的,在PatternTable 里面是没有我用红框圈出来的 tile 的,PatternTable 里面只有左侧其对称的图案,这就是“抠门”之四,相同的 tile 直接翻转利用,这里是水平翻转,同样的还有垂直翻转,这里就不举例了。

在某一帧的画面中,一屏的背景是 960 个 tile 索引有序的排列好,这个“有序”由游戏代码逻辑决定,游戏代码规定此时云在天上,那就定死了它在天上不能动,除非更改代码。

但是精灵有些不同,OAM 中的精灵条目有属性项专门控制精灵的位置(X, Y 坐标),理论上精灵一帧中精灵可以在任何位置,不过一个游戏有一个游戏的逻辑,比如说马里奥本身在地上走跑跳,不可能在天上飞是吧。

一般角色的位置是可以由 Controller,比如说手柄来控制的,大致的过程就是手柄按键向 CPU 发送信号,然后监测相应的按键更改 OAM 中的精灵的位置属性,之后 PPU 就会渲染到相应的位置

背景要渲染,精灵要渲染,它两的像素肯定是会重叠的,PPU 自有逻辑控制和选择哪个的像素输出,这留待后面慢慢说到。

关于这,有意思的一点是:如果第 0 个精灵的不透明像素与背景不透明的像素重叠,那么就会引起 sprite 0 hit,可以利用这个特点来 split creen (屏幕分割?),大致意思就是屏幕上只有一部分滚屏渲染。

emmm 感觉表达的不太准确,举例子说明:在玩超级马里奥的时候会发现顶部的分数,时间等信息是没有随着滚屏而跑出屏幕之外,而是相对静止在屏幕顶部:

这就是依靠 sprite 0 hit 做出来的效果。

另外还可以做出大片级的效果,最为津津乐道的就是忍者龙剑传的过长动画。

这妥妥的大片级效果啊!!!

至于渲染到屏幕上,渲染的顺序与我们看书写字的顺序一样,从上到下,从左到右,如下所示:

这个 PPU 输出的信号需要由 CRT 电视接收,俗称大屁股电视,就是屏幕后面嘿大一坨的那种电视。这种电视的大致显色原理为电子枪发射电子轰击带有荧光粉的荧光屏,荧光粉收到高速电子的激发而发光。

这里有几个术语需要知道,渲染的一行叫做 scanline,每一帧中有 240 条 scanline 可见,刚好对应着我们前面所说的 $256 \times 240$ 的像素。

渲染的每一帧之间都会有一段空隙,这是因为渲染到最后一行后电子枪需要回到左上角,这部分时间就叫做 vblank(垂直消隐)。这部分其实是触发了一个 NMI 中断,CPU 可以在这段时间内更新 NameTable、OAM 等信息用于下次渲染

这里强烈建议看看这个视频:

【中字】慢镜头下的电视工作原理科普(CRT/LED/OLED)_哔哩哔哩_bilibili

那所谓的渲染是怎么一个过程呢?

对于背景来说,在渲染某一屏背景之前,这一屏背景的 tile 索引一般来说是在 nametable 中已经存放好了的,根据 tile 索引去获取存放在 tile 里面的颜色信息和 AttributeTable 里面的颜色信息,两者组合起来就是实际颜色的索引

而精灵呢?精灵的 tile 索引和 Attribute 都存放在 OAM(正渲染的精灵条目实际存放在一个缓冲区),同样的根据 Attribute 和 tile 两者中的颜色信息组合成实际颜色的索引

然后 PPU 根据一些状态信息抉择是输出背景的颜色还是精灵的颜色,这个颜色信号发送给电视,电视输出到屏幕。

渲染是最为复杂的过程之一,寥寥几句肯定不够,这后面再详述吧。

最后再来简要介绍前面途中出现过的一个东西,Mapper,也叫 MMC(Memory Management Chip),它的主要作用就是解决游戏大小的限制,CPU 和 PPU 的地址空间有限,如果游戏很大那么就有可能映射不下,所以需要 Mapper 来控制当前地址空间映射到卡带的哪一块空间,术语叫做 bank switching,就是将卡带里面的空间分成一个一个的 bank,然后 Mapper 控制 bank 映射到相应的地址空间

好了,本文大致就说这么多,主要是想让大家先有个大致的概念,其实我写到后面已经感觉到弄巧成拙了,这些东西三言两语是说不清的,但我又想说清,可能有些地方表述的不太清楚,那就待我后面慢慢说到吧。

本文的话,主要对开头几张图有个印象,CPU 和 PPU 都有自己的总线,地址空间都主要由三部分构成,卡带里面有游戏的代码和游戏使用的图案,但是这两者分别映射到了 CPU 和 PPU 的地址空间。

背景使用的 tile 索引存放在 NameTable,其中包含了 Attribute Table,精灵使用的的 tile 索引和 Attribute 都放在 OAM 中。tile 本身有 2bit 的颜色信息,Attribute 中又有两位的颜色信息,组合起来就是实际颜色在调色板中的索引。

渲染的话就是 PPU 要取得 $256 \times 240$ 个像素点的颜色信息然后发送给 TV,渲染顺序为从上到下从左到右,每一帧的渲染之间都会有叫做 vblank 的空闲时间,这部分时间之内 CPU 就可以去更新 NameTable、OAM 中的一些信息用于下次渲染。

大概就先这么多吧,一些领域我也初次接触,关于 NES 中文基本没有资料,都是直接啃的英文资料,某些地方理解可能有些偏差,一些英文术语没怎么看到也懒得去找官方翻译,我也就直接使用英文了。有什么问题还请批评指正,也欢迎来同我交流,下一篇应该是真的讲述 CPU 6502 了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
RSA 签名/验签 (PHP为例),以及各个秘钥格式解析
躺平程序员老修
2023/09/05
1.1K0
PHP 加密与解密
在现代 Web 应用中,数据安全是至关重要的,尤其是处理用户的敏感信息时,如密码、个人身份信息、信用卡号等。为了保护这些敏感数据不被黑客窃取或篡改,我们常常需要使用加密技术对数据进行保护。PHP 作为一种广泛使用的服务器端脚本语言,也提供了多种加密和解密的方法,帮助开发者实现数据的安全保护。
繁依Fanyi
2025/02/06
4060
PHP 使用非对称加密算法(RSA)
  以前一直对客户端传给服务器的信息加密这一块一脸懵,如果app里面的用户登录信息被抓包拿到了,大写着 username:root,password:123456,  那不是很尴尬。
Lansonli
2021/10/09
1.9K0
RSA加密传输代码示例
涉及敏感数据的传输,双方最好约定使用加密解密。那RSA非对称加密就大有作为了。 服务端可以保留自己的私钥,发给客户端对应的公钥。这样就可以互相加解密了。php中rsa加解密实现:
Tinywan
2019/07/16
1.4K0
RSA密文过长加密解密 越过1024的解决代码
RSA (详见维基百科)算法是现今使用最广泛的公钥密码算法,也是号称地球上最安全的加密算法,与 md5 和 sha1 不同,到目前为止,也只有极短的RSA加密被破解。
Tinywan
2019/07/16
5.1K0
PHP7.1实现的AES与RSA加密操作示例
本文实例讲述了PHP7.1实现的AES与RSA加密操作。分享给大家供大家参考,具体如下:
PHP开发工程师
2021/06/03
1.4K0
PHP 实现 SHA256 with RSA 签名 (实例讲解)
泥豆芽儿 MT
2023/10/16
1.3K0
PHP的OpenSSL加密扩展学习(二):非对称加密
上篇文章,我们了解了关于对称和非对称加密的一些相关的理论知识,也学习了使用 OpenSSL 来进行对称加密的操作。今天,我们就更进一步,学习 OpenSSL 中的非对称加密是如何实现的。
硬核项目经理
2020/11/10
9070
基础入门-算法逆向&散列对称非对称&JS源码逆向&AES&DES&RSA&SHA
密文-有源码直接看源码分析算法(后端必须要有源码才能彻底知道) 密文-没有源码1、猜识别 2、看前端JS(加密逻辑是不是在前端) #算法加密-概念&分类&类型
没事就要多学习
2024/07/18
1650
基础入门-算法逆向&散列对称非对称&JS源码逆向&AES&DES&RSA&SHA
[Linux] 使用openssl实现RSA非对称加密
参数:genrsa 生成密钥 -out 输出到文件 rsa_private_key.pem 文件名 1024 长度
唯一Chat
2019/09/11
3.6K0
php加密笔记[通俗易懂]
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/111630.html原文链接:https://javaforall.cn
全栈程序员站长
2022/07/19
1.6K0
php加密笔记[通俗易懂]
PHP加密解密方法及常见问题解决方案(php对称加密和非对称加密示例)
php是一种流行的服务器端编程语言,广泛用于web应用程序开发中。在实际应用中,php加密解密是非常常见的操作。本文将介绍php中常见的加密解密方法,以及常见问题的解决方案。
超级小可爱
2024/02/24
9370
PHP的openssl加密扩展使用小结
枕边书
2018/01/04
1.7K0
使用PHP实现RSA或RSA2算法的签名和验签
 使用RSA算法除了加密解密之外(加密解密的代码可以查看本站PHP使用RSA算法加密解密数据 这篇文章),在支付等接口方面通常还会用到生成签名和验证签名操作,下面是PHP代码:
友儿
2022/09/09
8590
php openssl生成证书,php中使用OpenSSL生成证书及加密解密[通俗易懂]
摘要:这篇文章主要介绍了PHP中使用OpenSSL生成证书及加密解密,需要的朋友可以参考下依赖于OpenSSL扩展/*加密解密*/
全栈程序员站长
2022/11/04
2.4K0
PHP RSA密文过长加密解密,PHP RSA证书大小自动适配,PHP RSA分段加密
项目中需要加密超长json内容才发现rsa加密长度有限制,于是换一种思路:我们将原本需要加密的内容拆分为多个字符串,一段一段的加密,解密端也是一段一段的解密即可完成。
高久峰
2024/04/20
3300
前端(JS)与后端(PHP) 通过 RSA 实现加解密
在项目开发过程中,为了安全起见,通常不能以明文形式传递敏感信息,容易被截获,所以引入RSA。 1. 生成公钥和私钥 在终端(基于 Unix 的操作系统)中输入以下内容。生成私钥 openssl genrsa -out rsa_1024_priv.pem 1024 通过执行以下命令获取公钥 openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem 可以使用 cat 命令来查看 cat rsa_1024_priv.pem //
句小芒
2022/12/29
2K0
RSA加密解密(无数据大小限制,php、go、java互通实现)
RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题。明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段。
双鬼带单
2019/07/30
5K0
非对称加密笔记
这篇文章是摘抄自某个地方,现在看来这些解释ztm是yts,说了半天都说不清楚,后来我看了MIT的一个老师上课的视频才发现人家几句或说清楚的道理咱们tm的比划了半天 画了这么多图 还说不清。md! 请大家移步这里看 https://my.oschina.net/lilugirl2005/blog/3029999
lilugirl
2019/05/26
7330
Node.js 使用 RSA 做加密
假设 A 与 B 通信。A 和 B 都提供一个公开的公钥。A 把需要传递的信息,先用自己的私钥签名,再用 B 的公钥加密。B 接收到这串密文后,用自己的私钥解密,用 A 提供的公钥验签。
超超不会飞
2020/09/18
6.7K0
推荐阅读
相关推荐
RSA 签名/验签 (PHP为例),以及各个秘钥格式解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档