用C / C ++解码和匹配Chip 8操作码

时间:2011-07-08 04:10:19

标签: c++ c emulation decode opcode

我正在编写一个Chip 8仿真器作为仿真的介绍,我有点迷失。基本上,我已经读过Chip 8 ROM并将其存储在内存中的char数组中。然后,按照指南,我使用以下代码检索当前程序计数器(pc)上的操作码:

// Fetch opcode
opcode = memory[pc] << 8 | memory[pc + 1];

芯片8操作码每个2字节。这是一个指南中的代码,我模糊地理解为向内存[pc]添加8个额外的位空间(使用&lt;&lt; 8)然后将内存[pc + 1]与它合并(使用|)并将结果存储在操作码变量。

现在我已经隔离了操作码,但我真的不知道如何处理它。我正在使用this操作码表,我基本上在将我读取的十六进制操作码与该表中的操作码标识符匹配方面丢失了。另外,我意识到我正在阅读的许多操作码也包含操作数(我假设后一个字节?),这可能会使我的情况更加复杂。

帮助?!

4 个答案:

答案 0 :(得分:9)

基本上,一旦掌握了指令,就需要对其进行解码。例如,从您的操作码表:

if ((inst&0xF000)==0x1000)
{
  write_register(pc,(inst&0x0FFF)<<1);
}

并猜测,因为你每个指令访问rom两个字节,地址可能是一个(16位)字地址而不是一个字节地址所以我把它左移一个(你需要研究这些指令是如何编码的,操作码你提供的表格不足以做到这一点,而且无需做出假设。)

还有很多事情要发生,我不知道我是否在github示例中写了一些关于它的内容。我建议你创建一个获取函数,用于获取地址指令,读存储器功能,写存储器功能,读寄存器功能,写寄存器功能。我建议你的解码和执行函数解码,一次只执行一条指令。正常执行只是在循环中调用它,它提供了执行中断和类似事情的能力而无需额外的工作。它还模块化您的解决方案。通过创建fetch()read_mem_byte()read_mem_word()等函数。你模块化你的代码(性能略有代价),使得调试变得更加容易,因为你有一个地方可以看到寄存器或内存访问,并找出正在发生的事情。

根据您的问题以及您在此过程中的位置,我认为在编写模拟器之前您需要做的第一件事就是编写反汇编程序。作为一个固定的指令长度指令集(16位),使它更容易。您可以从rom中的某个有趣点开始,或者如果您愿意,可以从头开始,并解码您看到的所有内容。例如:

if ((inst&0xF000)==0x1000)
{
  printf("jmp 0x%04X\n",(inst&0x0FFF)<<1);
}

只有35条指令不应该采取但是一个下午,也许整个星期六,是你第一次解码指令(我假设根据你的问题)。反汇编程序成为模拟器的核心解码器。用仿真替换printf()s,甚至更好地保留printfs并只添加代码来模拟指令执行,这样你就可以按照执行。 (同样的交易有一个反汇编单个指令函数,为每个指令调用它,这成为你的模拟器的基础)。

您的理解需要更加模糊,因为获取代码行正在做什么,为了完成这项任务,您将必须对位操作有一个强烈的理解。

此外,我会将您提供的代码行称为错误或至少有风险。如果memory []是一个字节数组,编译器可能会使用字节大小的数学执行左移,导致零,然后零orred,第二个字节只导致第二个字节。

基本上编译器有权将其转为:

opcode = memory[pc] << 8) | memory[pc + 1];

进入这个:

opcode = memory[pc + 1];

根本不适合你,这是一个非常快速的解决方法:

opcode = memory[pc + 0];
opcode <<= 8;
opcode |= memory[pc + 1];

会为您省去一些麻烦。最小化优化将使编译器不会将每个操作的中间结果存储到ram,从而产生相同(所需)的输出/性能。

我上面写的和上面提到的指令集模拟器不是为了提高性能,而是为了可读性,可见性和希望教育。我会从那样的事情开始然后如果感兴趣的性能你将不得不重新写它。这个chip8仿真器,一旦经历过,将是一个从零开始的下午任务,所以一旦你完成这个,你第一次可以重写它可能在一个周末三四次,而不是一个巨大的任务(必须重写) )。 (缩略图一个人带我度过了一个周末,其中大部分都是。周围的msp430可能更像是一个晚上或两个人的工作。一次又一次地获得溢出标志是最大的任务,后来出现了)。无论如何,重点是,看看像mame源这样的东西,大多数(如果不是全部)这些指令集模拟器都是为执行速度而设计的,如果没有相当多的研究,很多都几乎无法读取。通常是大量的桌面驱动,有时很多C编程技巧等等。从可管理的东西开始,让它正常运行,然后担心提高它的速度或大小或可移植性等等。这个chip8的东西看起来是基于图形的,所以你还必须在位图/屏幕/任何地方处理大量的线条绘制和其他位操作。或者你可以只调用api或操作系统函数。基本上这个chip8的东西不是传统的寄存器指令集,也不是寻址模式和alu操作的清单。

答案 1 :(得分:4)

基本上 - 掩盖操作码的可变部分,并寻找匹配。然后使用变量部分。

例如1NNN就是跳跃。所以:

int a = opcode & 0xF000;
int b = opcode & 0x0FFF;
if(a == 0x1000)
   doJump(b);

然后游戏就是让你的代码变得快或小,或者优雅,如果你愿意的话。干净有趣!

答案 2 :(得分:3)

不同的CPU以不同的方式在内存中存储值。 Big endian机器以FF,CC的顺序在内存中存储类似$ FFCC的数字。 Little-endian机器以相反的顺序CC,FF存储字节(即首先是“小端”)。

CHIP-8架构是big endian,因此您运行的代码具有用big endian编写的指令和数据。

在您的语句“opcode = memory [pc]&lt;&lt; 8 | memory [pc + 1];”中,主机CPU(您的计算机的CPU)是小端还是大端无关紧要。它将始终以正确的顺序将16位大端值设置为整数。

有一些资源可能有所帮助:http://www.emulator101.com提供了CHIP-8仿真器教程以及一些通用仿真器技术。这个也很好:http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/

答案 3 :(得分:2)

你将不得不设置一堆不同的位掩码,以便从16位字中结合有限状态机获取实际操作码,以便解释那些操作码,因为它似乎存在一些并发症。如何编码操作码(即,某些操作码具有寄存器标识符等,而其他操作码具有单个标识符则非常直接)。

您的有限状态机基本上可以执行以下操作:

  1. 使用像0xF000这样的掩码获取操作码的第一个半字节。这将允许您“分类”操作码
  2. 根据步骤1中的函数类别,应用更多掩码来从操作码中获取寄存器值,或者使用操作码编码的任何其他变量,这将缩小需要调用的实际函数,如以及它的论点。
  3. 获得操作码和变量信息后,请查找固定长度的函数表,这些函数具有与操作码功能和操作码一起变量一致的相应处理程序。虽然您可以在状态机中硬编码每个操作码的功能名称,一旦您隔离了正确的功能,您使用每个操作码的功能指针初始化的表是一种更灵活的方法,将允许您更容易地修改代码功能(即,您可以轻松地在调试处理程序和“普通”处理程序之间切换等。)。