我正在努力学习仿真编程。我在40条指令下做了一个8号芯片的模拟器,因为我的音乐而活了下来。我现在希望能做一些更复杂的事情,比如冷笑。我遇到的问题是CPU指令的绝对数量。透过wiki.SuperFamicom.org 65c816 instruction listing看去,后面好像很痛。我在各种各样的网页上都看到过这样的注释:CPU是仿真器最容易实现的部分。
假设这是因为我做错了所以很难,我环顾四周,发现了一个简单的实现:SNES Emulator in 15 minutes大约900行代码。很容易搞定。
然后,从SNES Emulator in 15 minutes源代码中,我找到了cpu指令的位置。看起来比我想象的要简单得多。我不太明白,但这是几行代码,而不是一大堆代码。我注意到的第一件事是每个指令只有一个实现。如果你看看SuperFamicom中的表,你会发现它有

ADC #const
ADC (_db_),X
ADC (_db_,X)
ADC addr
ADC long
...

所有这些的模拟器源代码是:
// Note: op 0x100 means "NMI", 0x101 means "Reset", 0x102 means "IRQ". They are implemented in terms of "BRK".
// User is responsible for ensuring that WB() will not store into memory while Reset is being processed.
unsigned addr=0, d=0, t=0xFF, c=0, sb=0, pbits = op<0x100 ? 0x30 : 0x20;

// Define the opcode decoding matrix, which decides which micro-operations constitute
// any particular opcode. (Note: The PLA of 6502 works on a slightly different principle.)
const unsigned o8 = op / 32, o8m = 1u << (op%32);
// Fetch op'th item from a bitstring encoded in a data-specific variant of base64,
// where each character transmits 8 bits of information rather than 6.
// This peculiar encoding was chosen to reduce the source code size.
// Enum temporaries are used in order to ensure compile-time evaluation.
#define t(w8,w7,w6,w5,w4,w3,w2,w1,w0) if( \
        (o8<1?w0##u : o8<2?w1##u  : o8<3?w2##u : o8<4?w3##u : \
         o8<5?w4##u : o8<6?w5##u  : o8<7?w6##u : o8<8?w7##u : w8##u) & o8m)

t(0,0xAAAAAAAA,0x00000000,0x00000000,0x00000000,0xAAAAA2AA,0x00000000,0x00000000,0x00000000) { c = t; t += A + P.C; P.V = (c^t) & (A^t) & 0x80; P.C = t & 0x100; }

简而言之,我的一般问题是:
把CPU指令惊人的宇宙能量浓缩成一小段代码
15分钟内针对SNES模拟器的问题来源(以上部分):
如何解析指令?我看到了t(0, 0xAAAAAAAA, 0x00000000, ....)语句,但我不知道任何参数的数字从何而来,也不知道它们对整个代码意味着什么。
为什么ifo8 = op / 32
o8m = 1u << (op%32)的操作码具有具有2字节操作数的ADC或具有3字节操作数的ADC #const。这两种情况都是由代码实现的?
当我问:
ADC addrt(0, 0xAAAAAAAA, ...)dp中出现的_dp_srADC dp是什么意思?
ADC (_dp_)ADC sr,S有什么区别?(考虑到上述问题,可能是多余的。)

最佳答案

我不能回答所有这些问题,但dp表示直接页,这意味着指令采用单字节操作数,即直接页中的内存地址。直接页面寻址是6502零页面寻址模式的扩展,其中单字节地址指的是内存位置$00$FF。6502的16位导数有一个配置寄存器,它基本上将零页重新定位到另一个位置。
在您链接到的wiki页面中,表中的一些dp上有下划线,其他的是斜体。我假设它们都是斜体的,wiki标记不起作用。快速检查编辑链接支持这种假设(在wiki源代码中,它们都有下划线)。所以不要读任何东西。
在6502组件及其衍生物中,ADC dp,X表示……让我们举一个具体的例子…ADC $10,X意味着将$10加到寄存器X中的值以获得地址,然后从该地址加载一个值并将其加到累加器中。ADC ($10,X)添加额外级别的间接寻址:将$10添加到X以获取地址,从该地址加载值,将加载的值解释为另一个地址,然后从该地址加载值并将其添加到累加器。带圆括号的操作数总是添加一级间接寻址。
注意,可用的模式包括(dp,X)(dp),Y,括号相对于逗号和寄存器的位置很重要。使用(dp),Y时,Y的值将添加到第一个加载的值,以获取在第二个加载中使用的地址。
至于那个模拟器…代码高尔夫并不能提高可读性!我不认为你所发布的部分本身是可以理解的,我也不想追踪和阅读其他部分。但是t宏中的关键概念是位字符串。它的参数是一系列9位掩码,每个32位长,总共288位。因此,每个可能的操作码(其中256个)加上第一条注释中提到的3个伪操作码,都由这个288位长的位串中的一个位表示,剩余29位。
这就解释了o8o8m的结构。8位值分为3位部分(从提供给t的8个参数中选择一个参数)和5位部分(从所选参数中选择一个位)。大的?:链进行第一次选择,&1 << ...的组合进行选择。
然后,我们还有一个变量,叫做t。它与宏无关。给他们起同样的名字太残忍了。
也许我能搞清楚那个比特串在干什么。当操作码为低位时,o8(高位)将为0,因此?:链将使用w0,这是宏的最后一个参数。随着操作码的增加,选定的参数在参数列表中向左移动到w1,然后w2o8m选择器同样从右边开始向左移动(& (1<<0)是最右边的位,& (1<<1)是下一个位,等等),当所选位为1时,if条件将为真。值为:

0,          # opcodes $100 and up
0xAAAAAAAA, # opcodes $E0 to $FF
0x00000000, # opcodes $C0 to $DF
0x00000000, # opcodes $A0 to $BF
0x00000000, # opcodes $80 to $9F
0xAAAAA2AA, # opcodes $60 to $7F
0x00000000, # opcodes $40 to $5F
0x00000000, # opcodes $20 to $3F
0x00000000  # opcodes $00 to $1F

或二进制
0,                                  # opcodes $100 and up
0b10101010101010101010101010101010, # opcodes $E0 to $FF
0b00000000000000000000000000000000, # opcodes $C0 to $DF
0b00000000000000000000000000000000, # opcodes $A0 to $BF
0b00000000000000000000000000000000, # opcodes $80 to $9F
0x10101010101010101010001010101010, # opcodes $60 to $7F
0b00000000000000000000000000000000, # opcodes $40 to $5F
0b00000000000000000000000000000000, # opcodes $20 to $3F
0b00000000000000000000000000000000  # opcodes $00 to $1F

从右到左读取每一行,1处于与这些操作码对应的位置:$61$63$65$67$69$6D$6F$71$73$75$77$79$7B$7D$7F$E1$E3$E5$E7$E9$EB$ED$EF$F1$F3$F5$F7$F9$FB$FD$FFADCSBC
隐马尔可夫模型。。。这有点像和操作码的列表,但其中有些是错误的。
哦(我终于放弃了,看了更多的仿真器代码),那是一个NES仿真器,不是一个SNES仿真器,所以它只有6502个操作码。

关于c - 仿真实现CPU指令?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/17253090/

10-11 04:28