获取可变长度操作码和CPU时序

时间:2013-06-29 06:23:02

标签: c++ timing emulation opcodes

我目前正在尝试用C ++编写一个NES模拟器作为夏季编程项目,为下一学年的秋季学期做好准备(我有一段时间没有编写代码)。我已经编写了一个Chip8仿真器,所以我认为下一步是尝试编写一个NES仿真器。

无论如何,我被卡住了。我正在使用this网站作为我的操作码表,而且我正在遇到路障。在Chip8上,所有操作码都是两个字节长,所以它们很容易获取。但是,NES似乎有2或3个字节的操作码,具体取决于CPU所处的寻址模式。我想不出有什么简单的方法可以计算出每个操作码需要读取多少字节(我唯一的想法是创建非常长的if语句来检查操作码的第一个字节,以查看要读取的字节数。)

我也在弄清楚如何计算周期。如何在编程语言中创建时钟以使所有内容同步?

在一个不相关的方面,由于NES是小端,我是否需要读取programCounter + 1然后读取programCounter才能获得正确的操作码?

2 个答案:

答案 0 :(得分:1)

  

然而,NES似乎有两个或三个字节的操作码,具体取决于CPU所处的寻址模式。我想不出有什么简单的方法可以计算出每个操作码需要读取多少字节。 / p>

操作码仍然只有一个字节。额外字节指定具有显式操作数的那些指令的操作数。 要进行解码,您可以创建一个包含256个案例的switch - 块(实际上它不会是256个案例,因为某些操作码是非法的)。它可能看起来像这样:

opcode = ReadByte(PC++);
switch (opcode) {
...
case 0x4C:              // JMP abs
    address = ReadByte(PC++);
    address |= (uint16_t)ReadByte(PC) << 8;
    PC = address;
    cycles += 3;
    break;
...
}

编译器通常会为这些案例创建一个跳转表,因此您最终会得到相当高效(虽然略显臃肿)的代码。

另一种方法是创建一个每个操作码有一个条目的数组。这可能只是一个函数指针数组,每个操作码有一个函数 - 或者表可以包含一个指向一个函数的指针,用于获取操作数,一个用于执行实际操作,另外还有关于指令所需周期数的信息。这样你就可以分享很多代码。一个例子:

const Instruction INSTRUCTIONS[] =
{
    ...
    // 0x4C: JMP abs
    {&jmp, &abs_operand, 3},
    ...
};

  

我也在弄清楚如何计算周期。如何在编程语言中创建时钟以使所有内容同步?

计算CPU周期只是递增计数器的问题,就像我在上面的代码示例中所示。

要与CPU同步视频,最简单的方法是运行CPU对应于单个扫描线的活动显示周期的周期数,然后绘制一条扫描线,然后运行CPU的周期数对应到水平消隐期,然后重新开始。

当您开始涉及音频时,您如何同步内容可能取决于您正在使用的音频API。例如,某些API可能会通过向样本填充缓冲区并返回生成的样本数来向您发送回调。在这种情况下,您可以计算自上次回调以来模拟的CPU周期数,并根据该周期确定要生成的样本数。


  

在一个不相关的方面,由于NES是小端,我是否需要读取programCounter + 1然后读取programCounter才能获得正确的操作码?

由于操作码是单字节,并且6502上的指令没有像其他一些CPU架构那样打包成字,因此字节顺序并不重要。它 与16位操作数相关,但另一方面,PC和大多数手机也基于小端CPU。

答案 1 :(得分:0)

我在大约25年前写了6502的模拟器。

这是一个非常简单的处理器,所以无论是函数指针表还是交换机,都有256个字节的条目[交换机可以稍微短一点,因为在所有256个条目中没有有效的操作码,只有大约200个实际使用的操作码]。

现在,如果你想编写一个完全模拟指令时间的模拟器,那么你将获得更多乐趣。你基本上必须模拟每个组件如何工作的更多信息,并通过带有时钟的单元“涟漪”。这是相当多的工作,所以我可能,如果可能的话,忽略时间,只是让系统的速度取决于仿真器的速度。