任天堂的红白机系列的游戏应该是大家的童年了,红白机,又称FC,随着计算机技术的不断发展,现在市场上基本已经淘汰了红白机系列的硬件设备了。我偶尔的一个突发奇想,想要在体验一下红白机游戏的乐趣,于是乎我想到了用c++来实现一个模拟器的环境。
众所周知,红白机是一个比较简单的计算机系统,它由CPU,PPU,ROM,外设,卡带,显示器等组成。CPU当然是用来处理命令信息的,FC的CPU是6502,PPU相对来说可能对大家来说比较新奇一点 ,它的全称是图像控制器,可以把它当成是计算机中的显卡,它的显存是2KB,外设自然是大家手中的那个手柄,而卡带和显示器相信大家都明白它们的含义,就不在赘述。
上面已经大体上介绍了FC的大致组成,下面就正式进入模拟器的主体部分——CPU的模拟。
FC的CPU型号是6502,在模拟之前我们自然首先要清楚它的性能。大家肯定都学习过计算机组成原理,明白我们的的这个CPU是一个很典型的冯诺依曼结构,并且肯定也知道CPU中有地址总线,数据总线和各种寄存器,我们的6502CPU地址线是16位,数据线是8位(其寻址范围是多少?),它的寄存器也比较少,只有6个,分别是A,X,Y,P,SP,PC。这里只有PC(程序计数器)比较特殊,是16位的,而其他的寄存器都是8位的。SP是堆栈指针。一般堆栈空间分配在0x01FF~0x0100,堆栈的低8位是SP高8位是0x01。 6502的堆栈是向下增长的。P寄存器就是PSW处理器的状态位。A、X和Y是数据寄存器。那我们知道了这些基础知识之后应该要怎样去模拟6502CPU的工作环境呢?
首先,FC要加载模拟器的日志文件,如下:
std::ofstream logFile ("simplenes.log"), cpuTraceFile;sn::TeeStream logTee (logFile, std::cout);if (logFile.is_open() && logFile.good())sn::Log::get().setLogStream(logTee);elsesn::Log::get().setLogStream(std::cout);sn::Log::get().setLevel(sn::Info);
日志文件加载完毕之后,就轮到了我们的CPU开始工作了
else if (std::strcmp(argv[i], "--log-cpu") == 0){sn::Log::get().setLevel(sn::CpuTrace);cpuTraceFile.open("sn.cpudump");sn::Log::get().setCpuTraceStream(cpuTraceFile);LOG(sn::Info) << "CPU logging set." << std::endl;}
接着,就到了显示画面这一环节,这其中包括画面宽度,高度,缩放等的设置,
else if (std::strcmp(argv[i], "-s") == 0 || std::strcmp(argv[i], "--scale") == 0){float scale;std::stringstream ss;if (i + 1 < argc && ss << argv[i + 1] && ss >> scale)emulator.setVideoScale(scale);elseLOG(sn::Error) << "Setting scale from argument failed" << std::endl;++i;}else if (std::strcmp(argv[i], "-w") == 0 || std::strcmp(argv[i], "--width") == 0){int width;std::stringstream ss;if (i + 1 < argc && ss << argv[i + 1] && ss >> width)emulator.setVideoWidth(width);elseLOG(sn::Error) << "Setting width from argument failed" << std::endl;++i;}else if (std::strcmp(argv[i], "-H") == 0 || std::strcmp(argv[i], "--height") == 0){int height;std::stringstream ss;if (i + 1 < argc && ss << argv[i + 1] && ss >> height)emulator.setVideoHeight(height);elseLOG(sn::Error) << "Setting height from argument failed" << std::endl;++i;}
最后,则是要加载我们即将要开始玩的游戏的路径并设置玩家1和玩家2的键盘操作,运行该游戏。然后我们就可以开心的享受欢乐的游戏时光了。
sn::parseControllerConf("keybindings.conf", p1, p2);emulator.setKeys(p1, p2);emulator.run(path);