我正在尝试学习仿真编程。我已经完成了一个CHIP-8仿真器,40个以下的指令,并且因为我的音乐而活着。我现在希望做一些更复杂的事情,比如SNES。我遇到的问题是CPU指令的数量。透过wiki.SuperFamicom.org 65c816 instruction listing看,它看起来像是后方的痛苦。我在各种互联网页面上看到了这些和那里的注释,CPU是仿真器中最容易实现的部分。
假设它很难,因为我做错了,我环顾四周,发现了一个简单的暗示:SNES Emulator in 15 minutes这是大约900行代码。很容易完成。
然后,从SNES Emulator in 15 minutes源,我找到了CPU指令的位置。它看起来比我想的要简单得多。我真的不明白它,但它是几行代码而不是大量的代码。我注意到的第一件事是指令每个只有1个实施。如果您查看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; }
总之,我的一般问题:
在15分钟来源(上面发布的部分)中特定于SNES模拟器的问题:
t(0, 0xAAAAAAAA, 0x00000000, ....)
如何解析指令?我看到if
声明,但我不知道任何参数的数字来自哪里,或者它们对整体代码的含义。o8 = op / 32
和o8m = 1u << (op%32)
?ADC
的操作码ADC #const
具有2字节操作数,或ADC addr
具有3字节操作数。代码t(0, 0xAAAAAAAA, ...)
是否适用于这两种情况?我在问:
dp
,_dp_
和sr
中显示的ADC dp
,ADC (_dp_)
和ADC sr,S
是什么意思?ADC (_dp_,X)
和ADC dp,X
之间有什么区别? (考虑到上面的问题,可能是冗余的。)答案 0 :(得分:4)
我无法回答所有这些问题,但dp
代表Direct Page,这意味着该指令采用单字节操作数,该操作数是Direct Page中的内存地址。直接页面寻址是6502的零页面寻址模式的扩展,其中单字节地址指向存储器位置$00
到$FF
。 6502的16位导数有一个配置寄存器,它基本上将零页重新定位到备用位置.f
在您链接到的Wiki页面中,表格中的某些dp
会在其上显示下划线,其他的则以斜体显示。我假设它们都是斜体,并且wiki标记不起作用。快速检查Edit链接是否支持这一假设(在wiki源代码中,它们都有下划线)。所以不要读任何东西。
在6502汇编及其衍生词中,ADC dp,X
表示......让我们举一个具体的例子...... ADC $10,X
表示将$10
添加到寄存器{{1}中的值获取一个地址,然后从该地址加载一个值并将其添加到累加器。 X
添加了额外的间接级别:将ADC ($10,X)
添加到$10
以获取地址,从该地址加载值,将加载的值解释为另一个地址,并从< em> 地址并将其添加到累加器。带括号的操作数总是添加一个间接级别。
请注意,可用模式包括X
和(dp,X)
,并且括号相对于逗号和寄存器的位置非常重要。使用(dp),Y
时,(dp),Y
的值会添加到第一个加载的值中,以获取第二次加载时使用的地址。
至于那个模拟器......代码高尔夫不会提高可读性!我不认为你发布的那部分本身是可以理解的,而且我不想跟踪并阅读剩下的部分。但Y
宏中的关键概念是 bitstring 。它的参数是一系列9位掩码,每个32位长,总共288位。因此,每个可能的操作码(其中256个)加上第一个注释中提到的3个伪操作码,在这个288位长的位串中由一个位表示,剩下29位。
这解释了t
和o8
的构建。 8位值被分成3位部分(从提供给o8m
的8个参数中选择一个参数)和5位部分(从所选参数中选择一个位)。第t
个大链会进行第一次选择,?:
和&
的组合会进行选择。
然后,哦,看,我们也有一个名为1 << ...
的变量。它与宏无关。给他们相同的名字只是残酷。
也许我可以弄清楚bittring正在做什么。当操作码数较小时,t
(高位)将为0,因此o8
链将使用?:
,这是最后参数到宏。随着操作码的增加,所选参数向左移动到参数列表w0
,然后w1
... w2
选择器同样从右侧开始向左移动({{1最右边的位,o8m
是下一个,等等。)当所选位为1时,& (1<<0)
条件为真。值为:
& (1<<1)
或二进制
if
从右到左阅读每一行,1位于与这些操作码对应的位置: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
$61
$63
$65
{{1} } $67
$69
$6D
$6F
$71
$73
$75
$77
$79
{{1} } $7B
$7D
$7F
$E1
$E3
$E5
$E7
$E9
$EB
{{1} } $ED
$EF
$F1
$F3
$F5
嗯......那类似于$F7
和$F9
操作码的列表,但其中一些是错误的。
哦(我终于放弃并查看了更多的仿真器代码),这是一个 NES 仿真器,而不是 SNES 仿真器,所以它只有6502个操作码。