CPU

  • 首发公号:Rand_cs

NES 使用的 CPU 为 6502,但与标准的 6502 有些许不同,最大的不同在于 NES 使用的芯片拥有一个 pAPU(pseudo-Audio Processing Unit),使其能够处理声音。本文主要来介绍 6502,废话不多说,直接来看

内存布局

前文简要介绍了 CPU 和 PPU 的地址空间,再来看看:

CPU 的地址空间主要分为三部分,CPU RAM,内存映射寄存器,卡带中的内存 这三部分

由前文任天堂给出的总线图知道,地址总线有 16 位,所以可以寻址 216=64KB2^{16}=64KB216=64KB 的空间,来看看这 64KB 详细的布局情况:

6502 的汇编里 16 进制使用 $ 来表示,$0000-$07ff 这 2KB 是其内部的 RAM,而后的 $0800-$1fff 都是 $0000-$07ff 的镜像,意思是说 $0000, $0800, $1000, $1800 这 4 个地址映射到同一块物理内存

$2000-$2007 是 CPU 与 PPU(Picture Process Unit) 交互的寄存器,PPU 是用来处理图像的,可以看作是 NES 的显卡,这在后面的 PPU 中再详述。$2008-$3FFF 为 $2000-$2007 的镜像

$4000-$4020 这部分是其他的 I/O 寄存器,这在后面的文章中介绍。

$4020-$6000 是卡带中的一块内存,不是每个卡带都会用到。

$6000-$8000 映射到卡带的一个 RAM 芯片,这块区域主要用来存档,有的卡带支持此选项,但据我的经历,还没有遇到过这种可以存档的卡带(小时候一个游戏每次只能从头开始,有时玩着是真的心累啊)。另外 RAM 保存信息需要定时刷新,所以这块芯片肯定也是会配有电池

$8000-$10000 这 32KB 来存放游戏代码,其中游戏的代码又分为高低 bank,映射到不同的区域另外 $FFFA-$FFFF 是拿来存放中断向量的(不是所有空间),只有三种中断也就只有三个中断向量,这在后面中断详述。

关于 6502 地址空间的大致布局就先说那么多,有关 PPU,卡带 留待后面详述,本篇主要讲述 CPU 6502,那就先来仔细看看,其内部的 RAM 的布局情况。

RAM 布局

6502 下将 256 字节称为 Page,Zero Page 是内存的第一页,它主要通过特定的寻址方式(零页寻址)来使得 CPU 执行速度更快,具体的寻址方式后面再论。

$0100-$0200 用作栈使用,也是向下扩展的,栈就不多说了,大家应该都很熟悉了,

其他部分就没什么了,就是当作普通的内存使用,另外虽然一些手册资料里面没有明确说明,但我看了一些 NES 游戏编程,$200-$2ff 这 256 字节用作精灵条目,这对应着 PPU 关于精灵的 256 字节空间。

寄存器

Program Counter

16 bit,程序计数器 PC,存放下一条指令的地址,一条指令执行时就会更新这个寄存器的值,使它指向下一条指令的地址,与我们熟悉的 PC 一样,可以被分支指令修改等等,不再多说

Stack Pointer

栈指针 SP,6502 架构下的栈也是上下颠倒向下扩展的,即 push 一个元素 SP 减小,POP 一个元素 SP 增加

这个栈指针并不是直接指向栈内存的地址,而是一个最大值为 $FF 的偏移量。6502 的栈没有溢出检测,栈指针的值就是从 $00 到 $FF 之间回绕(wrap around),意思就是说 当前值为 $FF 时再往下移时就变成了 $00

Accumulator(A)

8 bit,用来存放运算结果或者从内存取回来的数据

Index Register X(X)

8 bit,用来作为循环的计数器或者特定寻址下的偏移量,也可以存放从内存取出来的数据,还能用来设置或者获取栈指针

Index Register Y(Y)

基本同 X,但是 Y 不能影响栈指针,就是不能用 Y 的值来设置栈指针

Processor Status§

状态寄存器,同 x86 下的 EFLAGS 寄存器,来看 6502 的状态寄存器记录了哪些信息:

  • Carry Flag©,进位标志(一般对于无符号数来说),如果最近一条指令有溢出——上溢:超出了 255,下溢:低于 0,则设置该 bit 为 1,比如说执行 255 + 1 会上溢,将 Carry Flag 置 1。有了 Carry Flag,使得可以进行长度超过 8 位的运算。
  • Zero Flag(Z),最近一条指令的结果是否为 0,如果是,则置 1,否则清零
  • Interrupt Disable(I),置 1 会使得系统忽略 IRQ 中断,清零则响应,SEI(Set Interrupt Disable) 指令将该位置 1,CLI(Clear Interrupt Disable)将该位清 0
  • Decimal Mode(D),该位用来将 6502 切换到 BCD 模式,但 NES 里面不用
  • Break Command(B),该位用来表示一个 BRK(Break) 指令在执行,该指令就是发出一个 IRQ 中断,类似 x86 下的 INT
  • Overflow Flag(V),溢出标志(一般对于有符号数来说),如果运算有溢出,则置 1
  • Negative Flag(N),该位就是运算结果的最高位

寻址方式

下面主要来说说寻址方式,6502 的寻址方式很多,感觉有些乱,来看:

指令格式:操作码 + 操作数

操作码占用 1 个字节

Accumulator

累加器寻址,操作数在累加器中,CPU 直接操作累加器,只有移位指令会使用该寻址模式,比如说 ASL(算数左移)

ASL A   ;A << 1

Implied

隐式寻址,使用隐含寻址的指令不要额外“显式”的操作数,比如说 PHA,将 累加器压栈,这个操作数 累加器是隐式的,所以叫做隐式寻址

Immediate

立即数寻址,即指令指出操作数的部分 给出的 不是操作数地址,而是操作数本身,这就是立即数寻址,也就是说这条指令需要的操作数没有在内存或者寄存器中,而是在指令本身里面,使用汇编指令时,在立即数的前面加上 # 表示“这是个立即数”,举个例子:

LDA  #$01    ;A = 0x01

Absolute

绝对寻址,指令中操作数部分为 操作数的绝对地址,举个例子:

AND $1234    ;将地址为1234的数据取出来与A相与;A = A & [1234]

Zero Page

绝对寻址的地址高字节为 0 的话,就是零页寻址,零页寻址指令的操作数部分只有 1 个字节,所以使用零页寻址的指令更短,执行更快,因为只用取一个操作数。因此经常使用的数据通常都放在零页。

LDA   $12    ;将$0012地址处的值加载到A

Relative

相对寻址,只用于分支指令,操作数是一个有符号数,相对于当前 PC 的偏移量。

BEQ   $12    ;如果状态寄存器Z位为0,则PC+=(2+$12),加2是因为跳过当前指令

Indirect

间接寻址,只有 jmp 跳转指令会使用间接寻址,此寻址方式有两操作数,比如说 aa 和 bb,它们形成一个 16 位地址 bbaa,aabb 上包含 xx,aabb + 1 上包含 yy,形成地址 yyxx,那么这个 jmp 指令会跳转到 yyxx,注意存放的位置顺序

$6C $34 $12   ;指令的机器码
JMP  ($1234)  ;跳转到$1234开始的两字节组成的地址

Zero Page X Indexed

零页 X 变址寻址,只有一个操作数,假如为 aa,则操作数的地址为 X + aa,所得地址一定要在零页之内,所以要作回绕处理。即 addr=(X+aa)%FFaddr = (X+aa)\%FFaddr=(X+aa)%FF

AND  $12, X  ;将(X+$12)%$FF处的数据与A相与

Zero Page Y Indexed

用法同上,不过只有 LDX(Load X Register) 和 STX(Store X Register) 指令会用这种寻址方式,看名字应知道这指令是什么作用,不再赘述

Absolute X Indexed

绝对 X 变址,在绝对寻址获得的地址基础上再加上 X 的值,就是操作数的地址

LDA  $1234, X ;将$1234+X处的值加载到A

Absolute Y Indexed

绝对 Y 变址,同上,只是 X 换为 Y

Indexed Indirect

X 变址间接寻址,有些复杂,来看任天堂的 NES 文档中给出的图:

先变址后间接,变址部分同 零页 X 变址(有回绕),只不过获得的地址是个间接地址,还要再进行间接寻址,如上面的例子,操作码为 aa,操作数为 bb,则间接地址为 bb + X,bb + X 开始的两字节形成的地址 yyxx 才是最终操作数的地址,操作数为 zz。

Indirect Indexed

间接 Y 变址寻址,同样来看图:

基本与上面相反,直接来说这个图的意思,指令 aa bb,aa 为操作码,bb 为操作数部分,是一个间接地址,这个地址开始的两字节形成一个新地址 yyxx,再做变址 yyxx+Y 得到的地址即为操作数 zz 的地址。

上述就是所有的寻址方式,13 种,属实有些多,不过也还是有规律可循,基本上就是基址,变址,间接这些的组合。

中断

中断的概念就不说了,由什么疑惑的可以看看我之前写的关于中断的文章:################,这里直接来看 6502 如何处理中断。

NES 有三种形式的中断,NMI,IRQ,RESET

中断向量表放在 $FFFA-$FFFF,中断向量表存放的是中断处理程序(Interrupt Handler) 的地址

IRQ

IRQ,Interrupt Request,Maskable Interrupts,主要是由一些卡带里的 Mapper 发起,Mapper 主要用来突破游戏 40KB 的限制(32KB的PRG+8KB的CHR),现下可以简单理解为指导卡带里的数据如何映射到 CPU 的地址空间,后面再详述这方面的内容。另外 BRK 指令也可以发起一个 IRQ,前面说过就跟 x86 下的 INT 一样的

IRQ 类型的中断处理程序的地址位于 $FFFE 和 $FFFF,这两个地址上存放的内容合起来才是真正的处理程序的地址

NMI

NMI,Non-Maskable Interrupt,不可屏蔽中断,其实可以屏蔽的,只是不会被 CPU 的状态寄存器的 I 位屏蔽,但是可以设置 $2000(PPU Control Register) 来屏蔽该中断 。

NMI 是当 V-Blank 发生时产生的一种中断,前文也简要说过 V-Blank,我们玩游戏时,整个屏幕大小为 256×240256 \times 240256×240,每一帧的图像都是从上到下一行一行的渲染,我们可见的部分有 256 行,渲染的每一行叫做 Scanline,当渲染完可见部分的 256 行之后回到最左上角准备渲染下一帧的这一段时间我们就叫做 V-Blank。而这段时间就要发起 NMI 中断,关于图像是 NES 最难的一部分,具体情况讲述 PPU 时再详述。

NMI 的中断处理程序的地址位于 $FFFA 和 $FFFB

Reset

重置,第一次启动或者按下重置按钮时发生的中断,中断处理程序的地址位于 $FFFC 和 $FFFD

中断处理过程

上述就是 NES 的三种类型中断,下面来看中断的处理过程,下面的步骤来自任天堂的文档,当个翻译工:

  1. 识别发生了哪种中断,查了查 6502 的 一个手册,虽然看不太懂里面的电路,但是明确表示了里面有相应的 detector,比如说 NMI edge detector,所以是能够检测识别出发生了哪一种中断
  2. 完成当前指令,不会一检测到中断就立马停下手头事务取处理中断
  3. 程序计数器,状态寄存器 压栈
  4. 设置状态寄存器的 I 位关中断
  5. PC 设置为中断处理程序的地址
  6. 执行中断处理程序
  7. 执行 RTI(Return From Interrupt) 指令从中断返回,程序计数器,状态寄存器出栈
  8. 回到原任务继续执行

这就是 6502 的中断处理过程,比较简单,过程和了解的 x86,MIPS 等等都差不多,中断处理需要花费 CPU 7 个 clock。

指令

下面来说说 6502 的指令,前面有说过一些,基本格式为 操作码 + 操作数,操作码占据一个字节,操作数部分要视寻址方式而定。

具体的指令我就不再本文叙述了,太多了,这个东西还是查手册为好。有兴趣的可以戳这个链接:

https://wiki.nesdev.org/w/index.php?title=CPU_unofficial_opcodes

https://wusiyu.me/6502-cpu汇编语言指令集/

只是再多说一点,操作码有官方给出的,还有一些非官方的,有一些游戏它是要使用非官方的操作码,所以再写 NES 模拟器的时候,若想要支持绝大多数游戏,则要将所有的指令,不论官方还是非官方的全都实现了

NES 的 CPU 6502 或者说 2A03 就说到这,有什么问题还请批评指正,也欢迎大家来同我交流讨论学习。下一篇讲述最困难也是最令人兴奋的 PPU 部分。

  • 首发公号:Rand_cs

红白机基本原理(二) CPU相关推荐

  1. 红白机原理(零)前言

    今天来聊聊 FC 游戏机,FC 的意思就是 Family Computer,虽然如今渐渐落寞被淘汰,但在当年的确是风靡全球,不负 Family Computer 这名字. FC 大家应该基本知道吧,e ...

  2. 使用c++模拟红白机——概论篇(一)

    任天堂的红白机系列的游戏应该是大家的童年了,红白机,又称FC,随着计算机技术的不断发展,现在市场上基本已经淘汰了红白机系列的硬件设备了.我偶尔的一个突发奇想,想要在体验一下红白机游戏的乐趣,于是乎我想 ...

  3. VirtualNES虚拟红白机

    VirtualNES虚拟红白机 -MD建档时间:2016/2/17 6:39 PM -CSDN发布:http://blog.csdn.net/winsenjiansbomber/article/det ...

  4. 【历史上的今天】10 月 18 日:Internet Explorer 7 正式发布;全球首家网络银行开业;“美版红白机” NES 诞生

    整理 | 王启隆 透过「历史上的今天」,从过去看未来,从现在亦可以改变未来. 今天是 2022 年 10 月 18 日,在 100 年前的今天,英国 BBC 广播电视台成立:BBC 是世界上最大的新闻 ...

  5. 任天堂“红白机之父”上村雅之去世,他曾给千万人带来欢乐

    行早 发自 凹非寺 量子位 报道 | 公众号 QbitAI 立命馆大学游戏研究中心12月9日发布讣告:红白机之父上村雅之教授于2021年12月6日去世,享年78岁. △ 图源:立命馆大学 上村雅之教授 ...

  6. 还记得当年陪你度过整个夏日的红白机吗?

    一转眼,暑假就要过去了,小编看着周围那些忙忙碌碌准备开学的小朋友们,不由想起自己的童年. 那个时候,最开心的就是暑假可以玩游戏机,也就是现在人们口中的FC红白机.虽然在现在看来都是像素级的游戏画面,但 ...

  7. 深度强化学习之:PPO训练红白机1942

    本篇是深度强化学习动手系列文章,自MyEncyclopedia公众号文章深度强化学习之:DQN训练超级玛丽闯关发布后收到不少关注和反馈,这一期,让我们实现目前主流深度强化学习算法PPO来打另一个红白机 ...

  8. 经典怀旧FCgame红白机小游戏在线网页合集版畅玩HTML网站源码

    经典怀旧FCgame红白机小游戏在线网页合集版畅玩HTML网站源码 ☑️ 编号:ym468 ☑️ 品牌:无 ☑️ 语言:ThinkPHP ☑️ 大小:4.7MB ☑️ 类型:经典怀旧FCgame ☑️ ...

  9. 小霸王其乐无穷~FC红白机游戏600合集(支持mac 12.x系统)

    FC游戏是任天堂红白机一FC(Family Computer),代表作品主要有<魂斗罗系列>.<93超级魂>.<沙罗曼蛇系列>.<超级玛丽>.<赤 ...

最新文章

  1. Linux strace 跟踪进程信息
  2. Maven之(二)Maven生命周期
  3. C++ Primer Plus 笔记第十章
  4. 搭建xss-platform平台
  5. Hadoop集群安装部署_分布式集群安装_02
  6. python中的scipy基础知识_Python机器学习(五十二)SciPy 基础功能
  7. QT入门引导 及其 案例讲解
  8. 逆波兰式求值 —Java
  9. spring boot之Spring Mvc开发和常用注解说明
  10. php出现网页无法运作怎么处理,该网页无法正常运作怎么解决?
  11. JSP内置对象和四大作用域
  12. 输出n行数字三角形(已修改)
  13. html的选项卡切换,纯css选项卡切换
  14. proxifier注册码
  15. Flutter学习记录——23.实现一个类似淘宝的商品展示页面
  16. 基于神经网络的蒙文手写字母识别的一些研究(综述)
  17. 社交电商大池,抖音也来了!
  18. 自然语言处理NLP——情感分析(Sentiment Analysis)简述
  19. 20181013优秀的软件推荐专栏(每日一次)
  20. matlab erf erfi,Mol.Plant|山东大学生科丁兆军团队揭示MPK14介导的生长素信号通过ERF13调节超长链脂肪酸的生物合成来控制侧根的发育...

热门文章

  1. 攀爬机器人 蝎子_洛洛历险记中五种机器人的原型,一个是蝎子,有一个不是生物...
  2. ESP32 之 ESP-IDF 教学(十八)—— 组件配置(KConfig)
  3. 概率质量函数(Probability mass function)
  4. 如何使用 CSS 定位 HTML 元素
  5. 高性能零售IT系统的建设06-当应对大量HTTP请求时兼顾性能、处理速度的架构设计
  6. 今天去元征科技,被SB面试了
  7. Qt5笔记之Qt5插件的生成与加载及json文件的读取
  8. 安卓APP跟iOSAPP之间的对比
  9. 大数据之电商分析系统(一)
  10. 【20】输入一个字符串,统计其中的字母、数字、其他字符的个数。