找了些NES资料,便开始着手NES模拟器的开发。
首先,看到6502处理器的描述之后,创建一个cpu.hpp文件,写出6502 cpu的寄存器结构体:
这样就确定了6502处理器的内部结构。接下来就是要模拟数值计算,一些寻址模式,无非就是直接寻址、间接寻址、或者再加上寄存器的值。
每条指令基本是这样,指令码是8bit,后面紧跟的操作数是8bit或16bit。我们要做的就是读取第一个字节,放到switch分支里,执行对应的代码,取后面的操作数进行计算。我暂时写了几条指令解析:
基本上整个核心模块就是这里,把每条指令的解析代码写出来,操作对应的内存就完成了模拟,图像的话,需要写个PPU,同样是通过指令传递一些数值给PPU,例如告诉它图片来源,精灵数据,地图数据,PPU就可以准备数据,进行绘图,再模拟硬件发出的一个60hz的中断信号,进行画面呈现即可。
现在,需要写一个内存文件来模拟nes的64kB内存读写,以及rom的加载。像这样:
紧接着,我们把rom加载进去,头文件是16字节,后面就是rom数据了。如果只有一个16kB的bank(rom存储单位),需要把16kB的数据复制一份到0xC000,这样0x80000xC0000xFFFF就被一个32kB的数据填满了,这样的话,位于0xFFFA~0xFFFF的3个中断向量读取位置里,才会有数据。我是这样写的:
一个cpu对象,一个mem对象,让前者操作后者,就是我们的目的,所以看看init函数:
这里进行了寄存器PC(指令指针)和寄存器SP(栈指针)的初始化,以及程序一开始必须执行的Reset中断。需要提的是,cpu是模拟的状态,所以里面的变量类型不是实际的指针,而是基于64kB内存的偏移数值,因为真正的内存地址需要malloc来申请。然后,看看系统中断函数干了什么:
无非就是从内存最后6个字节,每2字节存储的一个16bit地址中,拿出对应的中断类型存储的处理函数地址,设置PC指针到这个位置,然后继续执行代码。我写了个asm汇编,里面就标记了中断函数的地址,以及中断函数写了些什么:
看不懂的话,需要阅读相关文章,然后自己写个nes程序跑一下,对比着进行模拟器的编写才知道对不对。
下面是ppu绘图代码:
编写模拟器需要debug吧?所以得弄个界面,于是我用cocoa写了个UI,用来查看内存和cpu寄存器,比较简陋。
最好自己能写个简单demo来进行测试,然后在nes开发网找些demo来测试。主要是寻址模式写好,指令从官方文档dump下来(我用的python脚本把这上面的文档格式化成我项目里的指令数据),这样确保指令无误。并且使用了这里的具体指令代码(只需要实现几个函数即可,因为自己第一次就搞指令难免理解有误,写错都很难查)。
需要注意的是,原理看这个博客的讲解(属性表他描述有误,看这里的英文介绍),其他需要配合文档进行。
项目代码在这里,https://github.com/rexq57/RENES (未开发完)
参考资料:
NESASM教程
http://blog.csdn.net/kkk584520/article/details/40375219
nes文件格式
http://darhx.blog.51cto.com/7920146/1334093
从头编写NES模拟器
http://zke1ev3n.me/2016/03/12/%E4%BB%8E%E5%A4%B4%E7%BC%96%E5%86%99NES%E6%A8%A1%E6%8B%9F%E5%99%A8/
6502指令操作码排序
http://blog.sina.com.cn/s/blog_450c7f590100tn9u.html
标准6502指令文档
http://e-tradition.net/bytes/6502/6502_instruction_set.html
nes开发网站